diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2e5407f4..84ce6be9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,10 +50,6 @@ jobs: - name: Full install run: pip install -e '.[dev]' - - name: pylint - run: | - python -m pylint src tests - - name: mypy run: | python -m mypy --install-types --non-interactive diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 649e2140..fba0267e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,6 @@ default_language_version: python: python3 repos: - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: @@ -23,31 +19,21 @@ repos: args: ['--fix=lf'] - id: sort-simple-yaml - id: trailing-whitespace - - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 - hooks: - - id: codespell - additional_dependencies: ["tomli"] - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + - repo: https://github.com/psf/black + rev: 23.1.0 hooks: - - id: pyupgrade + - id: black - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + - repo: https://github.com/codespell-project/codespell + rev: v2.2.2 hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==23.1.20 - - flake8-comprehensions==3.10.1 - - flake8-debugger==4.1.2 - - flake8-string-format==0.3.0 - - repo: https://github.com/pycqa/bandit - rev: 1.7.4 + - id: codespell + additional_dependencies: ["tomli"] + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: 'v0.0.261' hooks: - - id: bandit - args: [-c, pyproject.toml] - additional_dependencies: ["toml"] + - id: ruff diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 870af8b4..baa8ad76 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -41,42 +41,39 @@ Request features on the `Issue Tracker`_. How to set up your development environment ------------------------------------------ -You need Python 3.8+ and the following tools: +You need Python 3.8+. -- Nox_ - -Install the package with development requirements: +- Clone the repository: .. code:: console - $ pip install nox + $ git clone https://github.com/iterative/dvclive + $ cd dvclive -.. _Nox: https://nox.thea.codes/ +- Set up a virtual environment: +.. code:: console -How to test the project ------------------------ + $ python -m venv .venv + $ source .venv/bin/activate -Run the full test suite: +Install in editable mode: .. code:: console - $ nox + $ pip install -e . -List the available Nox sessions: -.. code:: console - - $ nox --list-sessions +How to test the project +----------------------- -You can also run a specific Nox session. -For example, invoke the unit test suite like this: +Run the full test suite: .. code:: console - $ nox --session=tests + $ pytest -v tests -Unit tests are located in the ``tests`` directory, +Tests are located in the ``tests`` directory, and are written using the pytest_ testing framework. .. _pytest: https://pytest.readthedocs.io/ @@ -89,17 +86,17 @@ Open a `pull request`_ to submit changes to this project. Your pull request needs to meet the following guidelines for acceptance: -- The Nox test suite must pass without errors and warnings. -- Include unit tests. This project maintains 100% code coverage. +- The test suite must pass without errors and warnings. +- Include unit tests. - If your changes add functionality, update the documentation accordingly. Feel free to submit early, though—we can always iterate on this. -To run linting and code formatting checks, you can invoke a `lint` session in nox: +To run linting and code formatting checks, you can use `pre-commit`: .. code:: console - $ nox -s lint + $ pre-commit run --all-files It is recommended to open an issue before starting work on anything. This will allow a chance to talk it over with the owners and validate your approach. diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 292105d4..00000000 --- a/noxfile.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Automation using nox.""" -import glob -import os - -import nox - -nox.options.reuse_existing_virtualenvs = True -nox.options.sessions = "lint", "tests" -locations = "src", "tests" - - -@nox.session(python=["3.8", "3.9", "3.10", "3.11", "pypy3.8", "pypy3.9"]) -def tests(session: nox.Session) -> None: - session.install(".[dev]") - session.run( - "pytest", - "--cov", - "--cov-config=pyproject.toml", - *session.posargs, - env={"COVERAGE_FILE": f".coverage.{session.python}"}, - ) - - -@nox.session -def lint(session: nox.Session) -> None: - session.install("pre-commit") - session.install("-e", ".[dev]") - - args = *(session.posargs or ("--show-diff-on-failure",)), "--all-files" - session.run("pre-commit", "run", *args) - session.run("python", "-m", "mypy", "--install-types", "--non-interactive") - session.run("python", "-m", "pylint", *locations) - - -@nox.session -def safety(session: nox.Session) -> None: - """Scan dependencies for insecure packages.""" - session.install(".[tests]") - session.install("safety") - session.run("safety", "check", "--full-report") - - -@nox.session -def build(session: nox.Session) -> None: - session.install("build", "setuptools", "twine") - session.run("python", "-m", "build") - dists = glob.glob("dist/*") - session.run("twine", "check", *dists, silent=True) - - -@nox.session -def dev(session: nox.Session) -> None: - """Sets up a python development environment for the project.""" - args = session.posargs or ("venv",) - venv_dir = os.fsdecode(os.path.abspath(args[0])) - - session.log(f"Setting up virtual environment in {venv_dir}") - session.install("virtualenv") - session.run("virtualenv", venv_dir, silent=True) - - python = os.path.join(venv_dir, "bin/python") - session.run(python, "-m", "pip", "install", "-e", ".[dev]", external=True) diff --git a/pyproject.toml b/pyproject.toml index de1a9b21..e242adda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,29 +65,15 @@ ignore_missing_imports = true files = ["src", "tests"] enable_recursive_aliases = true -[tool.pylint.format] -max-line-length = 88 - -[tool.pylint.message_control] -enable = ["c-extension-no-member", "no-else-return"] -disable = [ - "format", "refactoring", "spelling", "design", - "invalid-name", "duplicate-code", "fixme", - "unused-wildcard-import", "cyclic-import", "wrong-import-order", - "wrong-import-position", "ungrouped-imports", "multiple-imports", - "logging-format-interpolation", "logging-fstring-interpolation", - "missing-function-docstring", "missing-module-docstring", - "missing-class-docstring", "raise-missing-from", "import-outside-toplevel", -] - -[tool.pylint.variables] -dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" -ignored-argument-names = "_.*|^ignored_|^unused_|args|kwargs" -generated-members = ["torch.*"] - [tool.codespell] ignore-words-list = "fpr" -[tool.bandit] -exclude_dirs = ["tests"] -skips = ["B101"] +[tool.ruff] +ignore = ["N818", "UP006", "UP007", "UP035", "UP038", "B905", "PGH003"] +select = ["F", "E", "W", "C90", "N", "UP", "YTT", "S", "BLE", "B", "A", "C4", "T10", "EXE", "ISC", "INP", "PIE", "T20", "PT", "Q", "RSE", "RET", "SLF", "SIM", "TID", "TCH", "INT", "ARG", "PGH", "PL", "TRY", "NPY", "RUF"] + +[tool.ruff.per-file-ignores] +"tests/*" = ["S101", "INP001", "SLF001", "ARG001", "ARG002", "ARG005", "PLR2004", "NPY002"] + +[tool.ruff.pylint] +max-args = 10 diff --git a/setup.cfg b/setup.cfg index 96a601d1..5398da18 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,9 +46,6 @@ tests = pytest-sugar==0.9.5 pytest-cov==3.0.0 pytest-mock==3.8.2 - pylint==2.15.0 - pylint-plugin-utils>=0.6 - mypy>=1.1.1 %(image)s %(plots)s %(markdown)s @@ -56,6 +53,7 @@ tests = dev = %(tests)s %(all)s + mypy>=1.1.1 mmcv = mmcv tf = @@ -97,20 +95,3 @@ exclude = tests tests.* where=src - -[flake8] -ignore= - # Whitespace before ':' - E203 - # Too many leading '#' for block comment - E266 - # Line break occurred before a binary operator - W503 - # unindexed parameters in the str.format, see: - # https://pypi.org/project/flake8-string-format/ - P1 -max_line_length = 88 -max-complexity = 15 -select = B,C,E,F,W,T4,B902,T,P -show_source = true -count = true diff --git a/src/dvclive/catalyst.py b/src/dvclive/catalyst.py index 2e6c1ef7..1f4f29c5 100644 --- a/src/dvclive/catalyst.py +++ b/src/dvclive/catalyst.py @@ -1,3 +1,4 @@ +# ruff: noqa: ARG002 from typing import Optional from catalyst import utils @@ -15,8 +16,9 @@ def __init__(self, model_file=None, live: Optional[Live] = None, **kwargs): def on_epoch_end(self, runner) -> None: for loader_key, per_loader_metrics in runner.epoch_metrics.items(): for key, value in per_loader_metrics.items(): - key = key.replace("/", "_") - self.live.log_metric(f"{loader_key}/{key}", float(value)) + self.live.log_metric( + f"{loader_key}/{key.replace('/', '_')}", float(value) + ) if self.model_file: checkpoint = utils.pack_checkpoint( @@ -28,5 +30,5 @@ def on_epoch_end(self, runner) -> None: utils.save_checkpoint(checkpoint, self.model_file) self.live.next_step() - def on_experiment_end(self, runner): # pylint: disable=unused-argument + def on_experiment_end(self, runner): self.live.end() diff --git a/src/dvclive/dvc.py b/src/dvclive/dvc.py index 2e7e7f7c..27dd0f28 100644 --- a/src/dvclive/dvc.py +++ b/src/dvclive/dvc.py @@ -1,4 +1,4 @@ -# pylint: disable=protected-access +# ruff: noqa: SLF001 import logging import os import random @@ -147,12 +147,12 @@ def get_random_exp_name(scm, baseline_rev): from dvc.repo.experiments.refs import ExpRefInfo # fmt: off - NOUNS = ('abac', 'abbs', 'aces', 'acid', 'acne', 'acre', 'acts', 'ados', 'adze', 'afro', 'agas', 'aged', 'ages', 'agio', 'agma', 'airs', 'airt', 'aits', 'akes', 'alap', 'albs', 'alga', 'ally', 'alto', 'amah', 'ambo', 'amie', 'amyl', 'ankh', 'apex', 'aqua', 'arcs', 'areg', 'aria', 'aril', 'arks', 'army', 'auks', 'aune', 'aura', 'awls', 'awns', 'axon', 'azan', 'baby', 'bade', 'bael', 'bags', 'bait', 'ball', 'banc', 'bang', 'bani', 'barb', 'bark', 'bate', 'bats', 'bawl', 'beak', 'bean', 'beep', 'belt', 'berk', 'beth', 'bias', 'bice', 'bids', 'bind', 'bise', 'bish', 'bite', 'boar', 'boat', 'body', 'boff', 'bold', 'boll', 'bolo', 'bomb', 'bond', 'book', 'boor', 'boot', 'bort', 'bosk', 'bots', 'bott', 'bout', 'bras', 'bree', 'brig', 'brio', 'buck', 'buhl', 'bump', 'bunk', 'bunt', 'buoy', 'byes', 'byte', 'cane', 'cant', 'caps', 'care', 'cart', 'cats', 'cedi', 'ceps', 'cere', 'chad', 'cham', 'chat', 'chay', 'chic', 'chin', 'chis', 'chiv', 'choc', 'chow', 'chum', 'ciao', 'cigs', 'clay', 'clip', 'clog', 'coal', 'coat', 'code', 'coed', 'cogs', 'coho', 'cole', 'cols', 'colt', 'conk', 'cons', 'cony', 'coof', 'cook', 'cool', 'coos', 'corm', 'cors', 'coth', 'cows', 'coze', 'crag', 'craw', 'cree', 'crib', 'cuds', 'cull', 'cult', 'curb', 'curn', 'curs', 'cusp', 'cuss', 'cwms', 'cyma', 'cyst', 'dabs', 'dado', 'daff', 'dais', 'daks', 'damn', 'dams', 'darg', 'dart', 'data', 'dawk', 'dawn', 'daws', 'daze', 'dean', 'debs', 'debt', 'deep', 'dees', 'dele', 'delf', 'dent', 'deys', 'dhow', 'digs', 'dirk', 'dita', 'diva', 'divs', 'doek', 'doge', 'dogs', 'dogy', 'dohs', 'doit', 'dole', 'doll', 'dolt', 'dona', 'dook', 'door', 'dops', 'doss', 'doxy', 'drab', 'drop', 'drum', 'duad', 'duct', 'duff', 'duke', 'dunk', 'dunt', 'ears', 'ease', 'eggs', 'eild', 'emeu', 'emus', 'envy', 'epha', 'eric', 'erns', 'esne', 'esse', 'ewes', 'expo', 'eyas', 'eyot', 'eyry', 'fare', 'farl', 'farm', 'feds', 'feel', 'fees', 'feme', 'fess', 'fibs', 'fids', 'fils', 'firm', 'fish', 'flab', 'flap', 'flea', 'flew', 'flex', 'flip', 'flit', 'flus', 'flux', 'foil', 'fond', 'food', 'fool', 'ford', 'fore', 'frit', 'friz', 'froe', 'funs', 'furl', 'fuss', 'fuzz', 'gaby', 'gaff', 'gale', 'gang', 'gaol', 'gape', 'gash', 'gaur', 'gaze', 'gear', 'genu', 'gest', 'geum', 'ghat', 'gigs', 'gimp', 'gird', 'girl', 'glee', 'glen', 'glia', 'glop', 'gnat', 'goad', 'goaf', 'gobs', 'gonk', 'good', 'goos', 'gore', 'gram', 'gray', 'grig', 'grip', 'grot', 'grub', 'gude', 'gula', 'gulf', 'guns', 'gust', 'gyms', 'gyro', 'hack', 'haet', 'hajj', 'hake', 'half', 'halm', 'hard', 'harl', 'hask', 'hate', "he'd", 'heck', 'heel', 'heir', 'help', 'hems', 'here', 'hill', 'hips', 'hits', 'hobo', 'hock', 'hogs', 'hold', 'holy', 'hood', 'hoot', 'hope', 'horn', 'hose', 'hour', 'hows', 'huck', 'hugs', 'huia', 'hulk', 'hull', 'hunk', 'hunt', 'huts', 'hymn', 'ibex', 'ices', 'iglu', 'impi', 'inks', 'inti', 'ions', 'iota', 'iron', 'jabs', 'jags', 'jake', 'jass', 'jato', 'jaws', 'jean', 'jeer', 'jerk', 'jest', 'jiao', 'jigs', 'jill', 'jinn', 'jird', 'jive', 'jock', 'joey', 'jogs', 'joss', 'jota', 'jots', 'juba', 'jube', 'judo', 'jump', 'junk', 'jura', 'juts', 'jynx', 'kago', 'kail', 'kaka', 'kale', 'kana', 'keek', 'keep', 'kefs', 'kegs', 'kerf', 'kern', 'keys', 'kibe', 'kick', 'kids', 'kifs', 'kill', 'kina', 'kind', 'kine', 'kite', 'kiwi', 'knap', 'knit', 'koas', 'kobs', 'kyat', 'lack', 'lahs', 'lair', 'lama', 'lamb', 'lame', 'lats', 'lava', 'lays', 'leaf', 'leak', 'leas', 'lees', 'leks', 'leno', 'libs', 'lich', 'lick', 'lien', 'lier', 'lieu', 'life', 'lift', 'limb', 'line', 'link', 'linn', 'lira', 'loft', 'loge', 'loir', 'long', 'loof', 'look', 'loot', 'lore', 'loss', 'lots', 'loup', 'love', 'luce', 'ludo', 'luke', 'lulu', 'lure', 'lush', 'magi', 'maid', 'main', 'mako', 'male', 'mana', 'many', 'mart', 'mash', 'mast', 'mate', 'math', 'mats', 'matt', 'maul', 'maya', 'mays', 'meal', 'mean', 'meed', 'mela', 'mene', 'mere', 'merk', 'mesh', 'mete', 'mice', 'milo', 'mime', 'mina', 'mine', 'mirk', 'miss', 'mobs', 'moit', 'mold', 'molt', 'mome', 'moms', 'monk', 'moot', 'mope', 'more', 'morn', 'mows', 'moxa', 'much', 'mung', 'mush', 'muss', 'myth', 'name', 'nard', 'nark', 'nave', 'navy', 'neck', 'newt', 'nibs', 'nims', 'nine', 'nock', 'noil', 'noma', 'nosh', 'nowt', 'nuke', 'oafs', 'oast', 'oats', 'obit', 'odor', 'okra', 'omer', 'oner', 'ones', 'orcs', 'ords', 'orfe', 'orle', 'ossa', 'outs', 'over', 'owls', 'pail', 'pall', 'palp', 'pams', 'pang', 'pans', 'pant', 'paps', 'pate', 'pats', 'paws', 'pear', 'peba', 'pech', 'pecs', 'peel', 'peer', 'pees', 'pein', 'peri', 'phon', 'pice', 'pita', 'pith', 'play', 'plop', 'plot', 'plow', 'plug', 'plum', 'polo', 'pomp', 'pond', 'pons', 'pony', 'poof', 'pope', 'poss', 'pots', 'pour', 'prad', 'prat', 'prep', 'prob', 'prof', 'prow', 'puck', 'puds', 'puke', 'puku', 'pump', 'puns', 'pupa', 'purl', 'pyre', 'quad', 'quay', 'quey', 'quiz', 'raid', 'rail', 'rain', 'raja', 'rale', 'rams', 'rand', 'rant', 'raps', 'rasp', 'razz', 'rede', 'reef', 'reif', 'rein', 'repp', 'rial', 'ribs', 'rick', 'rift', 'rill', 'rime', 'rims', 'ring', 'rins', 'rise', 'rite', 'rits', 'roam', 'robe', 'rods', 'roma', 'rook', 'rort', 'rotl', 'roup', 'roux', 'rube', 'rubs', 'ruby', 'rues', 'rugs', 'ruin', 'runs', 'ryas', 'sack', 'sacs', 'saga', 'sail', 'sale', 'salp', 'salt', 'sand', 'sang', 'sash', 'saut', 'says', 'scab', 'scow', 'scud', 'scup', 'scut', 'seal', 'seam', 'sech', 'seed', 'seep', 'seer', 'self', 'sena', 'send', 'sera', 'sere', 'shad', 'shah', 'sham', 'shay', 'shes', 'ship', 'shoe', 'sick', 'sida', 'sign', 'sike', 'sima', 'sine', 'sing', 'sinh', 'sink', 'sins', 'site', 'size', 'skat', 'skin', 'skip', 'skis', 'slaw', 'sled', 'slew', 'sley', 'slob', 'slue', 'slug', 'smut', 'snap', 'snib', 'snip', 'snob', 'snog', 'snot', 'snow', 'snub', 'snug', 'soft', 'soja', 'soke', 'song', 'sons', 'sook', 'sorb', 'sori', 'souk', 'soul', 'sous', 'soya', 'spit', 'stay', 'stew', 'stir', 'stob', 'stud', 'suds', 'suer', 'suit', 'sumo', 'sums', 'sups', 'suqs', 'suss', 'sway', 'syce', 'synd', 'taal', 'tach', 'taco', 'tads', 'taka', 'tale', 'tamp', 'tams', 'tang', 'tans', 'tape', 'tare', 'taro', 'tarp', 'tart', 'tass', 'taus', 'teat', 'teds', 'teff', 'tegu', 'tell', 'term', 'thar', 'thaw', 'tics', 'tier', 'tiff', 'tils', 'tilt', 'tint', 'tipi', 'tire', 'tirl', 'toby', 'tods', 'toea', 'toff', 'toga', 'toil', 'toke', 'tola', 'tole', 'tomb', 'toms', 'torc', 'tors', 'tort', 'tosh', 'tote', 'tret', 'trey', 'trio', 'trug', 'tuck', 'tugs', 'tule', 'tune', 'tuns', 'tuts', 'tyke', 'tyne', 'typo', 'ulna', 'umbo', 'unau', 'unit', 'upas', 'user', 'uvea', 'vacs', 'vane', 'vang', 'vans', 'vara', 'vase', 'veep', 'veer', 'vega', 'veil', 'vela', 'vent', 'vies', 'view', 'vina', 'vine', 'vise', 'vlei', 'volt', 'vows', 'wads', 'waft', 'wage', 'wain', 'walk', 'want', 'wart', 'wave', 'waws', 'weal', 'wean', 'weds', 'weep', 'weft', 'weir', 'weka', 'weld', 'wens', 'weys', 'whap', 'whey', 'whin', 'whit', 'whop', 'wide', 'wife', 'wind', 'wine', 'wino', 'wins', 'wire', 'wise', 'woes', 'wont', 'wool', 'work', 'worm', 'wort', 'yack', 'yank', 'yapp', 'yard', 'yate', 'yawl', 'yegg', 'yell', 'yeuk', 'yews', 'yips', 'yobs', 'yogi', 'yoke', 'yolk', 'yoni', 'zack', 'zags', 'zest', 'zhos', 'zigs', 'zila', 'zips', 'ziti', 'zoea', 'zone', 'zoon') # noqa: E501, Q000 - ADJECTIVES = ('about', 'above', 'abuzz', 'acerb', 'acold', 'acred', 'added', 'addle', 'adept', 'adult', 'adunc', 'adust', 'afoul', 'after', 'agape', 'agaze', 'agile', 'aging', 'agley', 'aglow', 'ahead', 'ahull', 'aided', 'alary', 'algal', 'alike', 'alive', 'alone', 'aloof', 'alpha', 'amber', 'amiss', 'amort', 'ample', 'amuck', 'angry', 'anile', 'apeak', 'apish', 'arced', 'areal', 'armed', 'aroid', 'ashen', 'aspen', 'astir', 'atilt', 'atrip', 'aulic', 'aural', 'awash', 'awful', 'awing', 'awned', 'axile', 'azoic', 'azure', 'baggy', 'baked', 'balky', 'bally', 'balmy', 'banal', 'bandy', 'bardy', 'bared', 'barer', 'barky', 'basal', 'based', 'baser', 'basic', 'batty', 'bawdy', 'beady', 'beaky', 'beamy', 'beaut', 'beefy', 'beery', 'beige', 'bendy', 'bifid', 'bijou', 'biped', 'birch', 'bitty', 'blame', 'bland', 'blank', 'blear', 'blest', 'blind', 'blond', 'blown', 'blowy', 'bluer', 'bluff', 'blunt', 'boned', 'bonny', 'boozy', 'bored', 'boric', 'bosky', 'bosom', 'bound', 'bovid', 'bowed', 'boxed', 'braky', 'brash', 'brief', 'briny', 'brisk', 'broad', 'broch', 'brood', 'brown', 'brute', 'buggy', 'bulgy', 'bumpy', 'burly', 'burnt', 'burry', 'bushy', 'busty', 'butch', 'buxom', 'cadgy', 'cagey', 'calmy', 'campy', 'canny', 'caped', 'cased', 'catty', 'cauld', 'cedar', 'cered', 'ceric', 'chary', 'cheap', 'cheek', 'chewy', 'chief', 'chill', 'chirk', 'choky', 'cissy', 'civil', 'cleft', 'coaly', 'color', 'comfy', 'comic', 'compo', 'conic', 'couth', 'coxal', 'crack', 'crank', 'crash', 'crass', 'crisp', 'cronk', 'cross', 'crude', 'cruel', 'crumb', 'cured', 'curly', 'curst', 'cushy', 'cutty', 'cynic', 'dated', 'dazed', 'dedal', 'deism', 'diazo', 'dicey', 'dingy', 'direr', 'dirty', 'dishy', 'dizzy', 'dolce', 'doped', 'dopey', 'dormy', 'dorty', 'dosed', 'dotal', 'dotty', 'dowdy', 'dowie', 'downy', 'dozen', 'drawn', 'dread', 'drear', 'dress', 'dried', 'ducky', 'duddy', 'dummy', 'dumpy', 'duple', 'dural', 'dusky', 'dusty', 'dutch', 'dying', 'eager', 'eaten', 'ebony', 'edged', 'eerie', 'eight', 'elder', 'elect', 'elfin', 'elite', 'empty', 'enate', 'enemy', 'epoxy', 'erect', 'ethic', 'every', 'extra', 'faced', 'faery', 'faint', 'famed', 'fancy', 'farci', 'fatal', 'fated', 'fatty', 'fazed', 'felon', 'fenny', 'ferny', 'fetal', 'fetid', 'fewer', 'fiery', 'fifty', 'filar', 'filmy', 'final', 'fined', 'finer', 'finny', 'fired', 'first', 'fishy', 'fixed', 'fizzy', 'flaky', 'flamy', 'flash', 'flawy', 'fleet', 'flory', 'flown', 'fluid', 'fluky', 'flush', 'focal', 'foggy', 'folio', 'forky', 'forte', 'forty', 'found', 'frail', 'frank', 'freed', 'freer', 'fresh', 'fried', 'front', 'frore', 'fuggy', 'funky', 'funny', 'furry', 'fusil', 'fussy', 'fuzzy', 'gabby', 'gamer', 'gamey', 'gamic', 'gammy', 'garni', 'gauge', 'gaunt', 'gauzy', 'gawky', 'gawsy', 'gemmy', 'genal', 'genic', 'ghast', 'gimpy', 'girly', 'glare', 'glary', 'glial', 'glued', 'gluey', 'godly', 'gooey', 'goofy', 'goosy', 'gouty', 'grade', 'grand', 'grapy', 'grave', 'gross', 'group', 'gruff', 'guest', 'gules', 'gulfy', 'gummy', 'gushy', 'gusty', 'gutsy', 'gutta', 'gypsy', 'gyral', 'hadal', 'hammy', 'handy', 'hardy', 'hasty', 'hated', 'hazel', 'heady', 'heapy', 'hefty', 'heigh', 'hempy', 'herby', 'hexed', 'hi-fi', 'hilly', 'hired', 'holey', 'honey', 'hooly', 'hoven', 'huger', 'hulky', 'humid', 'hunky', 'hyoid', 'idled', 'iliac', 'inane', 'incog', 'inert', 'inner', 'inter', 'iodic', 'ionic', 'irate', 'irony', 'itchy', 'jaggy', 'jammy', 'japan', 'jazzy', 'jerky', 'jetty', 'joint', 'jowly', 'juicy', 'jumpy', 'jural', 'kacha', 'kaput', 'kempt', 'keyed', 'kinky', 'known', 'kooky', 'kraal', 'laced', 'laigh', 'lairy', 'lamer', 'lardy', 'larky', 'lated', 'later', 'lathy', 'leady', 'leafy', 'leaky', 'leary', 'least', 'ledgy', 'leery', 'legal', 'leggy', 'lento', 'level', 'licht', 'licit', 'liege', 'light', 'liked', 'liney', 'lippy', 'lived', 'livid', 'loamy', 'loath', 'lobar', 'local', 'loony', 'loose', 'loral', 'losel', 'lousy', 'loved', 'lower', 'lowly', 'lowse', 'loyal', 'lucid', 'lucky', 'lumpy', 'lunar', 'lurid', 'lushy', 'lying', 'lyric', 'macho', 'macro', 'magic', 'major', 'malar', 'mangy', 'manky', 'manly', 'mardy', 'massy', 'mated', 'matte', 'mauve', 'mazed', 'mealy', 'meaty', 'medal', 'melic', 'mesic', 'mesne', 'messy', 'metal', 'miffy', 'milky', 'mined', 'minim', 'minor', 'minus', 'mired', 'mirky', 'misty', 'mixed', 'modal', 'model', 'moire', 'molar', 'moldy', 'moody', 'moony', 'mopey', 'moral', 'mossy', 'mothy', 'motor', 'mousy', 'moved', 'mucid', 'mucky', 'muddy', 'muggy', 'muley', 'mural', 'murky', 'mushy', 'muted', 'muzzy', 'myoid', 'naggy', 'naive', 'naked', 'named', 'nasty', 'natal', 'naval', 'nervy', 'newsy', 'nicer', 'niffy', 'nifty', 'ninth', 'nitty', 'nival', 'noble', 'nodal', 'noisy', 'non-U', 'north', 'nosed', 'noted', 'nowed', 'nubby', 'oaken', 'oared', 'oaten', 'obese', 'ocher', 'ochre', 'often', 'ohmic', 'oiled', 'olden', 'older', 'oleic', 'olive', 'optic', 'ortho', 'osmic', 'other', 'outer', 'ovoid', 'owing', 'owned', 'paced', 'pagan', 'paled', 'paler', 'pally', 'paper', 'pappy', 'parky', 'party', 'pasty', 'pavid', 'pawky', 'peaky', 'pearl', 'peart', 'peaty', 'pedal', 'peppy', 'perdu', 'perky', 'pesky', 'phony', 'piano', 'picky', 'piled', 'piney', 'pious', 'pique', 'pithy', 'platy', 'plump', 'plush', 'podgy', 'potty', 'power', 'prest', 'pricy', 'prima', 'prime', 'print', 'privy', 'prize', 'prone', 'proof', 'prosy', 'proud', 'proxy', 'pseud', 'pucka', 'pudgy', 'puffy', 'pukka', 'pupal', 'purer', 'pursy', 'pushy', 'pyoid', 'quack', 'quare', 'quasi', 'quiet', 'quits', 'rabic', 'rabid', 'radio', 'raked', 'randy', 'rapid', 'rarer', 'raspy', 'rathe', 'ratty', 'ready', 'reedy', 'reeky', 'refer', 'regal', 'riant', 'ridgy', 'right', 'riled', 'rimed', 'rindy', 'risen', 'risky', 'ritzy', 'rival', 'riven', 'robed', 'rocky', 'roily', 'roman', 'rooky', 'ropey', 'round', 'rowdy', 'ruddy', 'ruled', 'rummy', 'runic', 'runny', 'runty', 'rural', 'rusty', 'rutty', 'sable', 'salic', 'sandy', 'sappy', 'sarky', 'sassy', 'sated', 'saved', 'savvy', 'scald', 'scaly', 'scary', 'score', 'scrap', 'sedgy', 'seely', 'seral', 'sewed', 'shaky', 'sharp', 'sheen', 'shier', 'shill', 'shoal', 'shock', 'shoed', 'shore', 'short', 'shyer', 'silky', 'silly', 'silty', 'sixth', 'sixty', 'skint', 'slack', 'slant', 'sleek', 'slier', 'slimy', 'slung', 'small', 'smart', 'smoky', 'snaky', 'sneak', 'snide', 'snowy', 'snuff', 'so-so', 'soapy', 'sober', 'socko', 'solar', 'soled', 'solid', 'sonic', 'sooth', 'sooty', 'soppy', 'sorer', 'sound', 'soupy', 'spent', 'spicy', 'spiky', 'spiny', 'spiry', 'splay', 'split', 'sport', 'spumy', 'squat', 'staid', 'stiff', 'still', 'stoic', 'stone', 'stony', 'store', 'stout', 'straw', 'stray', 'strip', 'stung', 'suave', 'sudsy', 'sulfa', 'sulky', 'sunny', 'super', 'sural', 'surer', 'surfy', 'surgy', 'surly', 'swell', 'swept', 'swish', 'sworn', 'tabby', 'taboo', 'tacit', 'tacky', 'tamed', 'tamer', 'tangy', 'taped', 'tarot', 'tarry', 'tasty', 'tatty', 'taunt', 'tawie', 'teary', 'techy', 'telic', 'tenor', 'tense', 'tenth', 'tenty', 'tepid', 'terse', 'testy', 'third', 'tidal', 'tight', 'tiled', 'timid', 'tinct', 'tined', 'tippy', 'tipsy', 'tonal', 'toned', 'tonic', 'toric', 'total', 'tough', 'toxic', 'trade', 'treed', 'treen', 'trial', 'truer', 'tubal', 'tubby', 'tumid', 'tuned', 'tutti', 'twill', 'typal', 'typed', 'typic', 'umber', 'unapt', 'unbid', 'uncut', 'undue', 'undug', 'unfed', 'unfit', 'union', 'unlet', 'unmet', 'unwed', 'unwet', 'upper', 'upset', 'urban', 'utile', 'uveal', 'vagal', 'valid', 'vapid', 'varus', 'vatic', 'veiny', 'vital', 'vivid', 'vocal', 'vogie', 'volar', 'vying', 'wacky', 'wally', 'waney', 'warty', 'washy', 'waspy', 'waste', 'waugh', 'waxen', 'webby', 'wedgy', 'weeny', 'weepy', 'weest', 'weird', 'welsh', 'wersh', 'whist', 'white', 'whity', 'whole', 'wider', 'wight', 'winey', 'wired', 'wised', 'wiser', 'withy', 'wonky', 'woods', 'woozy', 'world', 'wormy', 'worse', 'worst', 'woven', 'wrath', 'wrier', 'wrong', 'wroth', 'xeric', 'yarer', 'yolky', 'young', 'yucky', 'yummy', 'zesty', 'zingy', 'zinky', 'zippy', 'zonal') # noqa: E501, Q000 + NOUNS = ('abac', 'abbs', 'aces', 'acid', 'acne', 'acre', 'acts', 'ados', 'adze', 'afro', 'agas', 'aged', 'ages', 'agio', 'agma', 'airs', 'airt', 'aits', 'akes', 'alap', 'albs', 'alga', 'ally', 'alto', 'amah', 'ambo', 'amie', 'amyl', 'ankh', 'apex', 'aqua', 'arcs', 'areg', 'aria', 'aril', 'arks', 'army', 'auks', 'aune', 'aura', 'awls', 'awns', 'axon', 'azan', 'baby', 'bade', 'bael', 'bags', 'bait', 'ball', 'banc', 'bang', 'bani', 'barb', 'bark', 'bate', 'bats', 'bawl', 'beak', 'bean', 'beep', 'belt', 'berk', 'beth', 'bias', 'bice', 'bids', 'bind', 'bise', 'bish', 'bite', 'boar', 'boat', 'body', 'boff', 'bold', 'boll', 'bolo', 'bomb', 'bond', 'book', 'boor', 'boot', 'bort', 'bosk', 'bots', 'bott', 'bout', 'bras', 'bree', 'brig', 'brio', 'buck', 'buhl', 'bump', 'bunk', 'bunt', 'buoy', 'byes', 'byte', 'cane', 'cant', 'caps', 'care', 'cart', 'cats', 'cedi', 'ceps', 'cere', 'chad', 'cham', 'chat', 'chay', 'chic', 'chin', 'chis', 'chiv', 'choc', 'chow', 'chum', 'ciao', 'cigs', 'clay', 'clip', 'clog', 'coal', 'coat', 'code', 'coed', 'cogs', 'coho', 'cole', 'cols', 'colt', 'conk', 'cons', 'cony', 'coof', 'cook', 'cool', 'coos', 'corm', 'cors', 'coth', 'cows', 'coze', 'crag', 'craw', 'cree', 'crib', 'cuds', 'cull', 'cult', 'curb', 'curn', 'curs', 'cusp', 'cuss', 'cwms', 'cyma', 'cyst', 'dabs', 'dado', 'daff', 'dais', 'daks', 'damn', 'dams', 'darg', 'dart', 'data', 'dawk', 'dawn', 'daws', 'daze', 'dean', 'debs', 'debt', 'deep', 'dees', 'dele', 'delf', 'dent', 'deys', 'dhow', 'digs', 'dirk', 'dita', 'diva', 'divs', 'doek', 'doge', 'dogs', 'dogy', 'dohs', 'doit', 'dole', 'doll', 'dolt', 'dona', 'dook', 'door', 'dops', 'doss', 'doxy', 'drab', 'drop', 'drum', 'duad', 'duct', 'duff', 'duke', 'dunk', 'dunt', 'ears', 'ease', 'eggs', 'eild', 'emeu', 'emus', 'envy', 'epha', 'eric', 'erns', 'esne', 'esse', 'ewes', 'expo', 'eyas', 'eyot', 'eyry', 'fare', 'farl', 'farm', 'feds', 'feel', 'fees', 'feme', 'fess', 'fibs', 'fids', 'fils', 'firm', 'fish', 'flab', 'flap', 'flea', 'flew', 'flex', 'flip', 'flit', 'flus', 'flux', 'foil', 'fond', 'food', 'fool', 'ford', 'fore', 'frit', 'friz', 'froe', 'funs', 'furl', 'fuss', 'fuzz', 'gaby', 'gaff', 'gale', 'gang', 'gaol', 'gape', 'gash', 'gaur', 'gaze', 'gear', 'genu', 'gest', 'geum', 'ghat', 'gigs', 'gimp', 'gird', 'girl', 'glee', 'glen', 'glia', 'glop', 'gnat', 'goad', 'goaf', 'gobs', 'gonk', 'good', 'goos', 'gore', 'gram', 'gray', 'grig', 'grip', 'grot', 'grub', 'gude', 'gula', 'gulf', 'guns', 'gust', 'gyms', 'gyro', 'hack', 'haet', 'hajj', 'hake', 'half', 'halm', 'hard', 'harl', 'hask', 'hate', "he'd", 'heck', 'heel', 'heir', 'help', 'hems', 'here', 'hill', 'hips', 'hits', 'hobo', 'hock', 'hogs', 'hold', 'holy', 'hood', 'hoot', 'hope', 'horn', 'hose', 'hour', 'hows', 'huck', 'hugs', 'huia', 'hulk', 'hull', 'hunk', 'hunt', 'huts', 'hymn', 'ibex', 'ices', 'iglu', 'impi', 'inks', 'inti', 'ions', 'iota', 'iron', 'jabs', 'jags', 'jake', 'jass', 'jato', 'jaws', 'jean', 'jeer', 'jerk', 'jest', 'jiao', 'jigs', 'jill', 'jinn', 'jird', 'jive', 'jock', 'joey', 'jogs', 'joss', 'jota', 'jots', 'juba', 'jube', 'judo', 'jump', 'junk', 'jura', 'juts', 'jynx', 'kago', 'kail', 'kaka', 'kale', 'kana', 'keek', 'keep', 'kefs', 'kegs', 'kerf', 'kern', 'keys', 'kibe', 'kick', 'kids', 'kifs', 'kill', 'kina', 'kind', 'kine', 'kite', 'kiwi', 'knap', 'knit', 'koas', 'kobs', 'kyat', 'lack', 'lahs', 'lair', 'lama', 'lamb', 'lame', 'lats', 'lava', 'lays', 'leaf', 'leak', 'leas', 'lees', 'leks', 'leno', 'libs', 'lich', 'lick', 'lien', 'lier', 'lieu', 'life', 'lift', 'limb', 'line', 'link', 'linn', 'lira', 'loft', 'loge', 'loir', 'long', 'loof', 'look', 'loot', 'lore', 'loss', 'lots', 'loup', 'love', 'luce', 'ludo', 'luke', 'lulu', 'lure', 'lush', 'magi', 'maid', 'main', 'mako', 'male', 'mana', 'many', 'mart', 'mash', 'mast', 'mate', 'math', 'mats', 'matt', 'maul', 'maya', 'mays', 'meal', 'mean', 'meed', 'mela', 'mene', 'mere', 'merk', 'mesh', 'mete', 'mice', 'milo', 'mime', 'mina', 'mine', 'mirk', 'miss', 'mobs', 'moit', 'mold', 'molt', 'mome', 'moms', 'monk', 'moot', 'mope', 'more', 'morn', 'mows', 'moxa', 'much', 'mung', 'mush', 'muss', 'myth', 'name', 'nard', 'nark', 'nave', 'navy', 'neck', 'newt', 'nibs', 'nims', 'nine', 'nock', 'noil', 'noma', 'nosh', 'nowt', 'nuke', 'oafs', 'oast', 'oats', 'obit', 'odor', 'okra', 'omer', 'oner', 'ones', 'orcs', 'ords', 'orfe', 'orle', 'ossa', 'outs', 'over', 'owls', 'pail', 'pall', 'palp', 'pams', 'pang', 'pans', 'pant', 'paps', 'pate', 'pats', 'paws', 'pear', 'peba', 'pech', 'pecs', 'peel', 'peer', 'pees', 'pein', 'peri', 'phon', 'pice', 'pita', 'pith', 'play', 'plop', 'plot', 'plow', 'plug', 'plum', 'polo', 'pomp', 'pond', 'pons', 'pony', 'poof', 'pope', 'poss', 'pots', 'pour', 'prad', 'prat', 'prep', 'prob', 'prof', 'prow', 'puck', 'puds', 'puke', 'puku', 'pump', 'puns', 'pupa', 'purl', 'pyre', 'quad', 'quay', 'quey', 'quiz', 'raid', 'rail', 'rain', 'raja', 'rale', 'rams', 'rand', 'rant', 'raps', 'rasp', 'razz', 'rede', 'reef', 'reif', 'rein', 'repp', 'rial', 'ribs', 'rick', 'rift', 'rill', 'rime', 'rims', 'ring', 'rins', 'rise', 'rite', 'rits', 'roam', 'robe', 'rods', 'roma', 'rook', 'rort', 'rotl', 'roup', 'roux', 'rube', 'rubs', 'ruby', 'rues', 'rugs', 'ruin', 'runs', 'ryas', 'sack', 'sacs', 'saga', 'sail', 'sale', 'salp', 'salt', 'sand', 'sang', 'sash', 'saut', 'says', 'scab', 'scow', 'scud', 'scup', 'scut', 'seal', 'seam', 'sech', 'seed', 'seep', 'seer', 'self', 'sena', 'send', 'sera', 'sere', 'shad', 'shah', 'sham', 'shay', 'shes', 'ship', 'shoe', 'sick', 'sida', 'sign', 'sike', 'sima', 'sine', 'sing', 'sinh', 'sink', 'sins', 'site', 'size', 'skat', 'skin', 'skip', 'skis', 'slaw', 'sled', 'slew', 'sley', 'slob', 'slue', 'slug', 'smut', 'snap', 'snib', 'snip', 'snob', 'snog', 'snot', 'snow', 'snub', 'snug', 'soft', 'soja', 'soke', 'song', 'sons', 'sook', 'sorb', 'sori', 'souk', 'soul', 'sous', 'soya', 'spit', 'stay', 'stew', 'stir', 'stob', 'stud', 'suds', 'suer', 'suit', 'sumo', 'sums', 'sups', 'suqs', 'suss', 'sway', 'syce', 'synd', 'taal', 'tach', 'taco', 'tads', 'taka', 'tale', 'tamp', 'tams', 'tang', 'tans', 'tape', 'tare', 'taro', 'tarp', 'tart', 'tass', 'taus', 'teat', 'teds', 'teff', 'tegu', 'tell', 'term', 'thar', 'thaw', 'tics', 'tier', 'tiff', 'tils', 'tilt', 'tint', 'tipi', 'tire', 'tirl', 'toby', 'tods', 'toea', 'toff', 'toga', 'toil', 'toke', 'tola', 'tole', 'tomb', 'toms', 'torc', 'tors', 'tort', 'tosh', 'tote', 'tret', 'trey', 'trio', 'trug', 'tuck', 'tugs', 'tule', 'tune', 'tuns', 'tuts', 'tyke', 'tyne', 'typo', 'ulna', 'umbo', 'unau', 'unit', 'upas', 'user', 'uvea', 'vacs', 'vane', 'vang', 'vans', 'vara', 'vase', 'veep', 'veer', 'vega', 'veil', 'vela', 'vent', 'vies', 'view', 'vina', 'vine', 'vise', 'vlei', 'volt', 'vows', 'wads', 'waft', 'wage', 'wain', 'walk', 'want', 'wart', 'wave', 'waws', 'weal', 'wean', 'weds', 'weep', 'weft', 'weir', 'weka', 'weld', 'wens', 'weys', 'whap', 'whey', 'whin', 'whit', 'whop', 'wide', 'wife', 'wind', 'wine', 'wino', 'wins', 'wire', 'wise', 'woes', 'wont', 'wool', 'work', 'worm', 'wort', 'yack', 'yank', 'yapp', 'yard', 'yate', 'yawl', 'yegg', 'yell', 'yeuk', 'yews', 'yips', 'yobs', 'yogi', 'yoke', 'yolk', 'yoni', 'zack', 'zags', 'zest', 'zhos', 'zigs', 'zila', 'zips', 'ziti', 'zoea', 'zone', 'zoon') # noqa: E501, Q000, N806 + ADJECTIVES = ('about', 'above', 'abuzz', 'acerb', 'acold', 'acred', 'added', 'addle', 'adept', 'adult', 'adunc', 'adust', 'afoul', 'after', 'agape', 'agaze', 'agile', 'aging', 'agley', 'aglow', 'ahead', 'ahull', 'aided', 'alary', 'algal', 'alike', 'alive', 'alone', 'aloof', 'alpha', 'amber', 'amiss', 'amort', 'ample', 'amuck', 'angry', 'anile', 'apeak', 'apish', 'arced', 'areal', 'armed', 'aroid', 'ashen', 'aspen', 'astir', 'atilt', 'atrip', 'aulic', 'aural', 'awash', 'awful', 'awing', 'awned', 'axile', 'azoic', 'azure', 'baggy', 'baked', 'balky', 'bally', 'balmy', 'banal', 'bandy', 'bardy', 'bared', 'barer', 'barky', 'basal', 'based', 'baser', 'basic', 'batty', 'bawdy', 'beady', 'beaky', 'beamy', 'beaut', 'beefy', 'beery', 'beige', 'bendy', 'bifid', 'bijou', 'biped', 'birch', 'bitty', 'blame', 'bland', 'blank', 'blear', 'blest', 'blind', 'blond', 'blown', 'blowy', 'bluer', 'bluff', 'blunt', 'boned', 'bonny', 'boozy', 'bored', 'boric', 'bosky', 'bosom', 'bound', 'bovid', 'bowed', 'boxed', 'braky', 'brash', 'brief', 'briny', 'brisk', 'broad', 'broch', 'brood', 'brown', 'brute', 'buggy', 'bulgy', 'bumpy', 'burly', 'burnt', 'burry', 'bushy', 'busty', 'butch', 'buxom', 'cadgy', 'cagey', 'calmy', 'campy', 'canny', 'caped', 'cased', 'catty', 'cauld', 'cedar', 'cered', 'ceric', 'chary', 'cheap', 'cheek', 'chewy', 'chief', 'chill', 'chirk', 'choky', 'cissy', 'civil', 'cleft', 'coaly', 'color', 'comfy', 'comic', 'compo', 'conic', 'couth', 'coxal', 'crack', 'crank', 'crash', 'crass', 'crisp', 'cronk', 'cross', 'crude', 'cruel', 'crumb', 'cured', 'curly', 'curst', 'cushy', 'cutty', 'cynic', 'dated', 'dazed', 'dedal', 'deism', 'diazo', 'dicey', 'dingy', 'direr', 'dirty', 'dishy', 'dizzy', 'dolce', 'doped', 'dopey', 'dormy', 'dorty', 'dosed', 'dotal', 'dotty', 'dowdy', 'dowie', 'downy', 'dozen', 'drawn', 'dread', 'drear', 'dress', 'dried', 'ducky', 'duddy', 'dummy', 'dumpy', 'duple', 'dural', 'dusky', 'dusty', 'dutch', 'dying', 'eager', 'eaten', 'ebony', 'edged', 'eerie', 'eight', 'elder', 'elect', 'elfin', 'elite', 'empty', 'enate', 'enemy', 'epoxy', 'erect', 'ethic', 'every', 'extra', 'faced', 'faery', 'faint', 'famed', 'fancy', 'farci', 'fatal', 'fated', 'fatty', 'fazed', 'felon', 'fenny', 'ferny', 'fetal', 'fetid', 'fewer', 'fiery', 'fifty', 'filar', 'filmy', 'final', 'fined', 'finer', 'finny', 'fired', 'first', 'fishy', 'fixed', 'fizzy', 'flaky', 'flamy', 'flash', 'flawy', 'fleet', 'flory', 'flown', 'fluid', 'fluky', 'flush', 'focal', 'foggy', 'folio', 'forky', 'forte', 'forty', 'found', 'frail', 'frank', 'freed', 'freer', 'fresh', 'fried', 'front', 'frore', 'fuggy', 'funky', 'funny', 'furry', 'fusil', 'fussy', 'fuzzy', 'gabby', 'gamer', 'gamey', 'gamic', 'gammy', 'garni', 'gauge', 'gaunt', 'gauzy', 'gawky', 'gawsy', 'gemmy', 'genal', 'genic', 'ghast', 'gimpy', 'girly', 'glare', 'glary', 'glial', 'glued', 'gluey', 'godly', 'gooey', 'goofy', 'goosy', 'gouty', 'grade', 'grand', 'grapy', 'grave', 'gross', 'group', 'gruff', 'guest', 'gules', 'gulfy', 'gummy', 'gushy', 'gusty', 'gutsy', 'gutta', 'gypsy', 'gyral', 'hadal', 'hammy', 'handy', 'hardy', 'hasty', 'hated', 'hazel', 'heady', 'heapy', 'hefty', 'heigh', 'hempy', 'herby', 'hexed', 'hi-fi', 'hilly', 'hired', 'holey', 'honey', 'hooly', 'hoven', 'huger', 'hulky', 'humid', 'hunky', 'hyoid', 'idled', 'iliac', 'inane', 'incog', 'inert', 'inner', 'inter', 'iodic', 'ionic', 'irate', 'irony', 'itchy', 'jaggy', 'jammy', 'japan', 'jazzy', 'jerky', 'jetty', 'joint', 'jowly', 'juicy', 'jumpy', 'jural', 'kacha', 'kaput', 'kempt', 'keyed', 'kinky', 'known', 'kooky', 'kraal', 'laced', 'laigh', 'lairy', 'lamer', 'lardy', 'larky', 'lated', 'later', 'lathy', 'leady', 'leafy', 'leaky', 'leary', 'least', 'ledgy', 'leery', 'legal', 'leggy', 'lento', 'level', 'licht', 'licit', 'liege', 'light', 'liked', 'liney', 'lippy', 'lived', 'livid', 'loamy', 'loath', 'lobar', 'local', 'loony', 'loose', 'loral', 'losel', 'lousy', 'loved', 'lower', 'lowly', 'lowse', 'loyal', 'lucid', 'lucky', 'lumpy', 'lunar', 'lurid', 'lushy', 'lying', 'lyric', 'macho', 'macro', 'magic', 'major', 'malar', 'mangy', 'manky', 'manly', 'mardy', 'massy', 'mated', 'matte', 'mauve', 'mazed', 'mealy', 'meaty', 'medal', 'melic', 'mesic', 'mesne', 'messy', 'metal', 'miffy', 'milky', 'mined', 'minim', 'minor', 'minus', 'mired', 'mirky', 'misty', 'mixed', 'modal', 'model', 'moire', 'molar', 'moldy', 'moody', 'moony', 'mopey', 'moral', 'mossy', 'mothy', 'motor', 'mousy', 'moved', 'mucid', 'mucky', 'muddy', 'muggy', 'muley', 'mural', 'murky', 'mushy', 'muted', 'muzzy', 'myoid', 'naggy', 'naive', 'naked', 'named', 'nasty', 'natal', 'naval', 'nervy', 'newsy', 'nicer', 'niffy', 'nifty', 'ninth', 'nitty', 'nival', 'noble', 'nodal', 'noisy', 'non-U', 'north', 'nosed', 'noted', 'nowed', 'nubby', 'oaken', 'oared', 'oaten', 'obese', 'ocher', 'ochre', 'often', 'ohmic', 'oiled', 'olden', 'older', 'oleic', 'olive', 'optic', 'ortho', 'osmic', 'other', 'outer', 'ovoid', 'owing', 'owned', 'paced', 'pagan', 'paled', 'paler', 'pally', 'paper', 'pappy', 'parky', 'party', 'pasty', 'pavid', 'pawky', 'peaky', 'pearl', 'peart', 'peaty', 'pedal', 'peppy', 'perdu', 'perky', 'pesky', 'phony', 'piano', 'picky', 'piled', 'piney', 'pious', 'pique', 'pithy', 'platy', 'plump', 'plush', 'podgy', 'potty', 'power', 'prest', 'pricy', 'prima', 'prime', 'print', 'privy', 'prize', 'prone', 'proof', 'prosy', 'proud', 'proxy', 'pseud', 'pucka', 'pudgy', 'puffy', 'pukka', 'pupal', 'purer', 'pursy', 'pushy', 'pyoid', 'quack', 'quare', 'quasi', 'quiet', 'quits', 'rabic', 'rabid', 'radio', 'raked', 'randy', 'rapid', 'rarer', 'raspy', 'rathe', 'ratty', 'ready', 'reedy', 'reeky', 'refer', 'regal', 'riant', 'ridgy', 'right', 'riled', 'rimed', 'rindy', 'risen', 'risky', 'ritzy', 'rival', 'riven', 'robed', 'rocky', 'roily', 'roman', 'rooky', 'ropey', 'round', 'rowdy', 'ruddy', 'ruled', 'rummy', 'runic', 'runny', 'runty', 'rural', 'rusty', 'rutty', 'sable', 'salic', 'sandy', 'sappy', 'sarky', 'sassy', 'sated', 'saved', 'savvy', 'scald', 'scaly', 'scary', 'score', 'scrap', 'sedgy', 'seely', 'seral', 'sewed', 'shaky', 'sharp', 'sheen', 'shier', 'shill', 'shoal', 'shock', 'shoed', 'shore', 'short', 'shyer', 'silky', 'silly', 'silty', 'sixth', 'sixty', 'skint', 'slack', 'slant', 'sleek', 'slier', 'slimy', 'slung', 'small', 'smart', 'smoky', 'snaky', 'sneak', 'snide', 'snowy', 'snuff', 'so-so', 'soapy', 'sober', 'socko', 'solar', 'soled', 'solid', 'sonic', 'sooth', 'sooty', 'soppy', 'sorer', 'sound', 'soupy', 'spent', 'spicy', 'spiky', 'spiny', 'spiry', 'splay', 'split', 'sport', 'spumy', 'squat', 'staid', 'stiff', 'still', 'stoic', 'stone', 'stony', 'store', 'stout', 'straw', 'stray', 'strip', 'stung', 'suave', 'sudsy', 'sulfa', 'sulky', 'sunny', 'super', 'sural', 'surer', 'surfy', 'surgy', 'surly', 'swell', 'swept', 'swish', 'sworn', 'tabby', 'taboo', 'tacit', 'tacky', 'tamed', 'tamer', 'tangy', 'taped', 'tarot', 'tarry', 'tasty', 'tatty', 'taunt', 'tawie', 'teary', 'techy', 'telic', 'tenor', 'tense', 'tenth', 'tenty', 'tepid', 'terse', 'testy', 'third', 'tidal', 'tight', 'tiled', 'timid', 'tinct', 'tined', 'tippy', 'tipsy', 'tonal', 'toned', 'tonic', 'toric', 'total', 'tough', 'toxic', 'trade', 'treed', 'treen', 'trial', 'truer', 'tubal', 'tubby', 'tumid', 'tuned', 'tutti', 'twill', 'typal', 'typed', 'typic', 'umber', 'unapt', 'unbid', 'uncut', 'undue', 'undug', 'unfed', 'unfit', 'union', 'unlet', 'unmet', 'unwed', 'unwet', 'upper', 'upset', 'urban', 'utile', 'uveal', 'vagal', 'valid', 'vapid', 'varus', 'vatic', 'veiny', 'vital', 'vivid', 'vocal', 'vogie', 'volar', 'vying', 'wacky', 'wally', 'waney', 'warty', 'washy', 'waspy', 'waste', 'waugh', 'waxen', 'webby', 'wedgy', 'weeny', 'weepy', 'weest', 'weird', 'welsh', 'wersh', 'whist', 'white', 'whity', 'whole', 'wider', 'wight', 'winey', 'wired', 'wised', 'wiser', 'withy', 'wonky', 'woods', 'woozy', 'world', 'wormy', 'worse', 'worst', 'woven', 'wrath', 'wrier', 'wrong', 'wroth', 'xeric', 'yarer', 'yolky', 'young', 'yucky', 'yummy', 'zesty', 'zingy', 'zinky', 'zippy', 'zonal') # noqa: E501, Q000, N806 # fmt: on while True: - adjective = random.choice(ADJECTIVES) # nosec B311 - noun = random.choice(NOUNS) # nosec B311 + adjective = random.choice(ADJECTIVES) # noqa: S311 + noun = random.choice(NOUNS) # noqa: S311 name = f"{adjective}-{noun}" exp_ref = ExpRefInfo(baseline_sha=baseline_rev, name=name) if not scm.get_ref(str(exp_ref)): diff --git a/src/dvclive/error.py b/src/dvclive/error.py index 674e5340..3b795b03 100644 --- a/src/dvclive/error.py +++ b/src/dvclive/error.py @@ -27,3 +27,11 @@ class InvalidParameterTypeError(DvcLiveError): def __init__(self, val: Any): self.val = val super().__init__(f"Parameter type {type(val)} is not supported.") + + +class InvalidReportModeError(DvcLiveError): + def __init__(self, val): + super().__init__( + f"`report` can only be `None`, `auto`, `html`, `notebook` or `md`. " + f"Got {val} instead." + ) diff --git a/src/dvclive/fastai.py b/src/dvclive/fastai.py index ac160b0d..a1750c2c 100644 --- a/src/dvclive/fastai.py +++ b/src/dvclive/fastai.py @@ -77,7 +77,6 @@ def after_fit(self): if _inside_fine_tune() and not self.freeze_stage_ended: self.freeze_stage_ended = True else: - if hasattr(self, "save_model"): - if self.save_model.last_saved_path: - self.live.log_artifact(str(self.save_model.last_saved_path)) + if hasattr(self, "save_model") and self.save_model.last_saved_path: + self.live.log_artifact(str(self.save_model.last_saved_path)) self.live.end() diff --git a/src/dvclive/huggingface.py b/src/dvclive/huggingface.py index 79baf51b..442357fa 100644 --- a/src/dvclive/huggingface.py +++ b/src/dvclive/huggingface.py @@ -1,3 +1,4 @@ +# ruff: noqa: ARG002 from typing import Optional from transformers import ( diff --git a/src/dvclive/keras.py b/src/dvclive/keras.py index a0e82b2f..3773fed5 100644 --- a/src/dvclive/keras.py +++ b/src/dvclive/keras.py @@ -1,12 +1,9 @@ +# ruff: noqa: ARG002 import os from typing import Dict, Optional -from tensorflow.keras.callbacks import ( # noqa pylint: disable=import-error, no-name-in-module - Callback, -) -from tensorflow.keras.models import ( # noqa pylint: disable=import-error, no-name-in-module - load_model, -) +from tensorflow.keras.callbacks import Callback +from tensorflow.keras.models import load_model from dvclive import Live from dvclive.utils import standardize_metric_name @@ -25,24 +22,18 @@ def __init__( self.save_weights_only = save_weights_only self.live = live if live is not None else Live(**kwargs) - def on_train_begin(self, logs=None): # pylint: disable=unused-argument + def on_train_begin(self, logs=None): if ( - self.live._resume # pylint: disable=protected-access + self.live._resume # noqa: SLF001 and self.model_file is not None and os.path.exists(self.model_file) ): if self.save_weights_only: - self.model.load_weights( # noqa pylint: disable=access-member-before-definition - self.model_file - ) + self.model.load_weights(self.model_file) else: - self.model = ( # noqa pylint: disable=attribute-defined-outside-init - load_model(self.model_file) - ) + self.model = load_model(self.model_file) - def on_epoch_end( - self, epoch: int, logs: Optional[Dict] = None - ): # pylint: disable=unused-argument + def on_epoch_end(self, epoch: int, logs: Optional[Dict] = None): logs = logs or {} for metric, value in logs.items(): self.live.log_metric(standardize_metric_name(metric, __name__), value) @@ -54,7 +45,5 @@ def on_epoch_end( self.live.log_artifact(self.model_file) self.live.next_step() - def on_train_end( - self, logs: Optional[Dict] = None - ): # pylint: disable=unused-argument + def on_train_end(self, logs: Optional[Dict] = None): self.live.end() diff --git a/src/dvclive/lightning.py b/src/dvclive/lightning.py index 6706dc0f..bddff586 100644 --- a/src/dvclive/lightning.py +++ b/src/dvclive/lightning.py @@ -1,4 +1,4 @@ -# pylint: disable=protected-access +# ruff: noqa: ARG002 import inspect from typing import Any, Dict, Optional @@ -39,7 +39,7 @@ def __init__( run_name: Optional[str] = "dvclive_run", prefix="", experiment=None, - dir: Optional[str] = None, # noqa pylint: disable=redefined-builtin + dir: Optional[str] = None, # noqa: A002 resume: bool = False, report: Optional[str] = "auto", save_dvc_exp: bool = False, @@ -58,7 +58,7 @@ def __init__( self._experiment = experiment self._version = run_name # Force Live instantiation - self.experiment # noqa pylint: disable=pointless-statement + self.experiment # noqa: B018 @property def name(self): @@ -82,11 +82,7 @@ def experiment(self): """ if self._experiment is not None: return self._experiment - else: - assert ( - rank_zero_only.rank == 0 - ), "tried to init log dirs in non global_rank=0" - self._experiment = Live(**self._live_init) + self._experiment = Live(**self._live_init) return self._experiment @@ -96,21 +92,19 @@ def version(self): @rank_zero_only def log_metrics(self, metrics: Dict[str, Any], step: Optional[int] = None): - assert ( - rank_zero_only.rank == 0 # type: ignore - ), "experiment tried to log from global_rank != 0" self.experiment.step = step for metric_name, metric_val in metrics.items(): - if is_tensor(metric_val): - metric_val = metric_val.cpu().detach().item() - metric_name = standardize_metric_name(metric_name, __name__) - self.experiment.log_metric(name=metric_name, val=metric_val) + val = metric_val + if is_tensor(val): + val = val.cpu().detach().item() + name = standardize_metric_name(metric_name, __name__) + self.experiment.log_metric(name=name, val=val) if _should_call_next_step(): - if step == self.experiment._latest_studio_step: + if step == self.experiment._latest_studio_step: # noqa: SLF001 # We are in log_eval_end_metrics but there has been already # a studio request sent with `step`. # We decrease the number to bypass `live.studio._get_unsent_datapoints` - self.experiment._latest_studio_step -= 1 + self.experiment._latest_studio_step -= 1 # noqa: SLF001 self.experiment.next_step() @rank_zero_only diff --git a/src/dvclive/live.py b/src/dvclive/live.py index 10955cc7..f3ba2300 100644 --- a/src/dvclive/live.py +++ b/src/dvclive/live.py @@ -21,7 +21,12 @@ mark_dvclive_only_ended, mark_dvclive_only_started, ) -from .error import InvalidDataTypeError, InvalidParameterTypeError, InvalidPlotTypeError +from .error import ( + InvalidDataTypeError, + InvalidParameterTypeError, + InvalidPlotTypeError, + InvalidReportModeError, +) from .plots import PLOT_TYPES, SKLEARN_PLOTS, Image, Metric, NumpyEncoder from .report import BLANK_NOTEBOOK_REPORT, make_report from .serialize import dump_json, dump_yaml, load_yaml @@ -39,7 +44,7 @@ class Live: def __init__( self, - dir: str = "dvclive", # noqa pylint: disable=redefined-builtin + dir: str = "dvclive", # noqa: A002 resume: bool = False, report: Optional[str] = "auto", save_dvc_exp: bool = False, @@ -192,13 +197,11 @@ def _init_report(self): else: self._report_mode = "html" elif self._report_mode not in {None, "html", "notebook", "md"}: - raise ValueError( - "`report` can only be `None`, `auto`, `html`, `notebook` or `md`" - ) + raise InvalidReportModeError(self._report_mode) logger.debug(f"{self._report_mode=}") @property - def dir(self) -> str: + def dir(self) -> str: # noqa: A003 return self._dir @property @@ -220,10 +223,7 @@ def plots_dir(self) -> str: @property def report_file(self) -> Optional[str]: if self._report_mode in ("html", "md", "notebook"): - if self._report_mode == "notebook": - suffix = "html" - else: - suffix = self._report_mode + suffix = "html" if self._report_mode == "notebook" else self._report_mode return os.path.join(self.dir, f"report.{suffix}") return None @@ -269,7 +269,7 @@ def log_image(self, name: str, val): if not Image.could_log(val): raise InvalidDataTypeError(name, type(val)) - if isinstance(val, str) or isinstance(val, Path): + if isinstance(val, (str, Path)): from PIL import Image as ImagePIL val = ImagePIL.open(val) @@ -329,7 +329,7 @@ def log_artifact(self, path: StrPath): if self._dvc_repo is not None: try: stage = self._dvc_repo.add(path) - except Exception as e: # pylint: disable=broad-except + except Exception as e: # noqa: BLE001 logger.warning(f"Failed to dvc add {path}: {e}") return @@ -386,18 +386,7 @@ def end(self): if self._dvcyaml: self.make_dvcyaml() - if self._inside_dvc_exp and self._dvc_repo: - dir_spec = PathSpec.from_lines("gitwildmatch", [self.dir]) - outs_spec = PathSpec.from_lines( - "gitwildmatch", [str(o) for o in self._dvc_repo.index.outs] - ) - paths_to_track = [ - f - for f in self._dvc_repo.scm.untracked_files() - if (dir_spec.match_file(f) and not outs_spec.match_file(f)) - ] - if paths_to_track: - self._dvc_repo.scm.add(paths_to_track) + self._ensure_paths_are_tracked_in_dvc_exp() if "done" not in self._studio_events_to_skip: response = False @@ -419,19 +408,7 @@ def end(self): else: self.make_report() - if self._save_dvc_exp: - from dvc.exceptions import DvcException - - try: - self._experiment_rev = self._dvc_repo.experiments.save( - name=self._exp_name, - include_untracked=self._include_untracked, - force=True, - ) - except DvcException as e: - logger.warning(f"Failed to save experiment:\n{e}") - finally: - mark_dvclive_only_ended() + self.save_dvc_exp() if self._dvc_repo and not self._inside_dvc_exp: from dvc.exceptions import DvcException @@ -464,3 +441,32 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self._inside_with = False self.end() + + def _ensure_paths_are_tracked_in_dvc_exp(self): + if self._inside_dvc_exp and self._dvc_repo: + dir_spec = PathSpec.from_lines("gitwildmatch", [self.dir]) + outs_spec = PathSpec.from_lines( + "gitwildmatch", [str(o) for o in self._dvc_repo.index.outs] + ) + paths_to_track = [ + f + for f in self._dvc_repo.scm.untracked_files() + if (dir_spec.match_file(f) and not outs_spec.match_file(f)) + ] + if paths_to_track: + self._dvc_repo.scm.add(paths_to_track) + + def save_dvc_exp(self): + if self._save_dvc_exp: + from dvc.exceptions import DvcException + + try: + self._experiment_rev = self._dvc_repo.experiments.save( + name=self._exp_name, + include_untracked=self._include_untracked, + force=True, + ) + except DvcException as e: + logger.warning(f"Failed to save experiment:\n{e}") + finally: + mark_dvclive_only_ended() diff --git a/src/dvclive/optuna.py b/src/dvclive/optuna.py index dd7c5d35..9954b484 100644 --- a/src/dvclive/optuna.py +++ b/src/dvclive/optuna.py @@ -1,3 +1,4 @@ +# ruff: noqa: ARG002 from dvclive import Live @@ -20,27 +21,24 @@ def _log_metrics(self, values, live): if isinstance(self.metric_name, str): if len(values) > 1: # Broadcast default name for multi-objective optimization. - names = [ - "{}_{}".format(self.metric_name, i) for i in range(len(values)) - ] + names = [f"{self.metric_name}_{i}" for i in range(len(values))] else: names = [self.metric_name] - else: - if len(self.metric_name) != len(values): - raise ValueError( - "Running multi-objective optimization " - "with {} objective values, but {} names specified. " - "Match objective values and names," - "or use default broadcasting.".format( - len(values), len(self.metric_name) - ) + elif len(self.metric_name) != len(values): + raise ValueError( + "Running multi-objective optimization " + "with {} objective values, but {} names specified. " + "Match objective values and names," + "or use default broadcasting.".format( + len(values), len(self.metric_name) ) + ) - else: - names = [*self.metric_name] + else: + names = [*self.metric_name] - metrics = {name: val for name, val in zip(names, values)} + metrics = dict(zip(names, values)) for k, v in metrics.items(): live.summary[k] = v diff --git a/src/dvclive/plots/image.py b/src/dvclive/plots/image.py index cfba9437..f914d921 100644 --- a/src/dvclive/plots/image.py +++ b/src/dvclive/plots/image.py @@ -19,11 +19,11 @@ def could_log(val: object) -> bool: return True if val.__class__.__module__ == "numpy": return True - if isinstance(val, PurePath) or isinstance(val, str): + if isinstance(val, (PurePath, str)): return True return False - def dump(self, val, **kwargs) -> None: + def dump(self, val, **kwargs) -> None: # noqa: ARG002 if val.__class__.__module__ == "numpy": from PIL import Image as ImagePIL diff --git a/src/dvclive/plots/metric.py b/src/dvclive/plots/metric.py index 74f66def..68548805 100644 --- a/src/dvclive/plots/metric.py +++ b/src/dvclive/plots/metric.py @@ -16,9 +16,11 @@ class Metric(Data): def could_log(val: object) -> bool: if isinstance(val, (int, float)): return True - if val.__class__.__module__ == "numpy": - if val.__class__.__name__ in NUMPY_SCALARS: - return True + if ( + val.__class__.__module__ == "numpy" + and val.__class__.__name__ in NUMPY_SCALARS + ): + return True return False @property diff --git a/src/dvclive/plots/sklearn.py b/src/dvclive/plots/sklearn.py index f0534d94..0547d51f 100644 --- a/src/dvclive/plots/sklearn.py +++ b/src/dvclive/plots/sklearn.py @@ -21,7 +21,7 @@ def output_path(self) -> Path: @staticmethod def could_log(val: object) -> bool: - if isinstance(val, tuple) and len(val) == 2: + if isinstance(val, tuple) and len(val) == 2: # noqa: PLR2004 return True return False @@ -125,7 +125,7 @@ def get_properties(): "y_label": "Predicted Label", } - def dump(self, val, **kwargs) -> None: + def dump(self, val, **kwargs) -> None: # noqa: ARG002 cm = [ {"actual": str(actual), "predicted": str(predicted)} for actual, predicted in zip(val[0], val[1]) diff --git a/src/dvclive/report.py b/src/dvclive/report.py index b47b266b..3545fff7 100644 --- a/src/dvclive/report.py +++ b/src/dvclive/report.py @@ -1,3 +1,4 @@ +# ruff: noqa: SLF001 import base64 import json from pathlib import Path @@ -9,6 +10,7 @@ from dvc_render.table import TableRenderer from dvc_render.vega import VegaRenderer +from dvclive.error import InvalidReportModeError from dvclive.plots import SKLEARN_PLOTS, Image, Metric from dvclive.plots.sklearn import SKLearnPlot from dvclive.serialize import load_yaml @@ -18,9 +20,6 @@ from dvclive import Live -# noqa pylint: disable=protected-access - - BLANK_NOTEBOOK_REPORT = """
DVCLive Report @@ -176,4 +175,4 @@ def make_report(live: "Live"): elif live._report_mode == "md": render_markdown(renderers, live.report_file) else: - raise ValueError(f"Invalid `mode` {live._report_mode}.") + raise InvalidReportModeError(live._report_mode) diff --git a/src/dvclive/serialize.py b/src/dvclive/serialize.py index 7e2c4e90..889dd5b0 100644 --- a/src/dvclive/serialize.py +++ b/src/dvclive/serialize.py @@ -22,7 +22,7 @@ def load_yaml(path, typ="safe"): try: return yaml.load(fd.read()) except _YAMLError: - raise YAMLFileCorruptedError(path) + raise YAMLFileCorruptedError(path) from _YAMLError def get_yaml(): diff --git a/src/dvclive/studio.py b/src/dvclive/studio.py index cd2f2a18..14bb5bd4 100644 --- a/src/dvclive/studio.py +++ b/src/dvclive/studio.py @@ -1,4 +1,4 @@ -# pylint: disable=protected-access +# ruff: noqa: SLF001 import logging import os from pathlib import Path diff --git a/src/dvclive/utils.py b/src/dvclive/utils.py index e4b65c4b..0f903bca 100644 --- a/src/dvclive/utils.py +++ b/src/dvclive/utils.py @@ -6,14 +6,13 @@ from pathlib import Path from platform import uname -# noqa pylint: disable=unused-import - def run_once(f): def wrapper(*args, **kwargs): if not wrapper.has_run: wrapper.has_run = True return f(*args, **kwargs) + return None wrapper.has_run = False return wrapper @@ -107,10 +106,9 @@ def matplotlib_installed() -> bool: def inside_colab() -> bool: try: from google import colab # noqa: F401 - - return True except ImportError: return False + return True def inside_notebook() -> bool: diff --git a/src/dvclive/xgb.py b/src/dvclive/xgb.py index 8f11ea8a..0c158a92 100644 --- a/src/dvclive/xgb.py +++ b/src/dvclive/xgb.py @@ -1,3 +1,4 @@ +# ruff: noqa: ARG002 from typing import Optional from xgboost.callback import TrainingCallback diff --git a/tests/conftest.py b/tests/conftest.py index 8cbfb9c2..1c452d86 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,17 @@ -# pylint: disable=redefined-outer-name +# ruff: noqa: ARG002 import sys import pytest from dvc_studio_client.env import STUDIO_ENDPOINT, STUDIO_REPO_URL, STUDIO_TOKEN -@pytest.fixture +@pytest.fixture() def tmp_dir(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) - yield tmp_path + return tmp_path -@pytest.fixture +@pytest.fixture() def mocked_dvc_repo(tmp_dir, mocker): _dvc_repo = mocker.MagicMock() _dvc_repo.index.stages = [] @@ -22,8 +22,8 @@ def mocked_dvc_repo(tmp_dir, mocker): return _dvc_repo -@pytest.fixture -def dvc_repo(tmp_dir): # pylint: disable=redefined-outer-name +@pytest.fixture() +def dvc_repo(tmp_dir): from dvc.repo import Repo from scmrepo.git import Git @@ -34,24 +34,23 @@ def dvc_repo(tmp_dir): # pylint: disable=redefined-outer-name @pytest.fixture(autouse=True) -def capture_wrap(): +def _capture_wrap(): # https://github.com/pytest-dev/pytest/issues/5502#issuecomment-678368525 sys.stderr.close = lambda *args: None sys.stdout.close = lambda *args: None - yield @pytest.fixture(autouse=True) -def mocked_webbrowser_open(mocker): +def _mocked_webbrowser_open(mocker): mocker.patch("webbrowser.open") @pytest.fixture(autouse=True) -def mocked_CI(monkeypatch): +def _mocked_ci(monkeypatch): monkeypatch.setenv("CI", "false") -@pytest.fixture +@pytest.fixture() def mocked_studio_post(mocker, monkeypatch): valid_response = mocker.MagicMock() valid_response.status_code = 200 diff --git a/tests/plots/test_image.py b/tests/plots/test_image.py index 3fc50228..22eb97ef 100644 --- a/tests/plots/test_image.py +++ b/tests/plots/test_image.py @@ -2,12 +2,11 @@ import pytest from PIL import Image -# pylint: disable=unused-argument from dvclive import Live from dvclive.plots import Image as LiveImage -def test_PIL(tmp_dir): +def test_pil(tmp_dir): live = Live() img = Image.new("RGB", (10, 10), (250, 250, 250)) live.log_image("image.png", img) @@ -18,7 +17,7 @@ def test_PIL(tmp_dir): def test_invalid_extension(tmp_dir): live = Live() img = Image.new("RGB", (10, 10), (250, 250, 250)) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="unknown file extension"): live.log_image("image.foo", img) diff --git a/tests/plots/test_metric.py b/tests/plots/test_metric.py index 4e2ca561..0f490504 100644 --- a/tests/plots/test_metric.py +++ b/tests/plots/test_metric.py @@ -3,7 +3,6 @@ import numpy as np import pytest -# pylint: disable=unused-argument from dvclive import Live from dvclive.plots.metric import Metric from dvclive.plots.utils import NUMPY_INTS, NUMPY_SCALARS diff --git a/tests/plots/test_sklearn.py b/tests/plots/test_sklearn.py index dbd4db90..97c8772a 100644 --- a/tests/plots/test_sklearn.py +++ b/tests/plots/test_sklearn.py @@ -1,3 +1,4 @@ +# ruff: noqa: N806 import json import pytest @@ -6,10 +7,8 @@ from dvclive import Live from dvclive.plots.sklearn import SKLearnPlot -# pylint: disable=redefined-outer-name, unused-argument - -@pytest.fixture +@pytest.fixture() def y_true_y_pred_y_score(): from sklearn.datasets import make_classification from sklearn.ensemble import RandomForestClassifier diff --git a/tests/test_dvc.py b/tests/test_dvc.py index 084d12d4..9a1eebb1 100644 --- a/tests/test_dvc.py +++ b/tests/test_dvc.py @@ -1,4 +1,3 @@ -# pylint: disable=unused-argument,protected-access import os import pytest diff --git a/tests/test_frameworks/test_catalyst.py b/tests/test_frameworks/test_catalyst.py index c2ea44f7..81a2ae4d 100644 --- a/tests/test_frameworks/test_catalyst.py +++ b/tests/test_frameworks/test_catalyst.py @@ -1,3 +1,4 @@ +# ruff: noqa: N806 import os import catalyst @@ -9,10 +10,8 @@ from dvclive.catalyst import DVCLiveCallback from dvclive.plots import Metric -# pylint: disable=redefined-outer-name, unused-argument - -@pytest.fixture +@pytest.fixture() def runner(): return dl.SupervisedRunner( engine=catalyst.utils.torch.get_available_engine(cpu=True), @@ -25,7 +24,7 @@ def runner(): # see: # https://github.com/catalyst-team/catalyst/blob/e99f9/tests/catalyst/callbacks/test_batch_overfit.py -@pytest.fixture +@pytest.fixture() def runner_params(): from torch.utils.data import DataLoader, TensorDataset diff --git a/tests/test_frameworks/test_fastai.py b/tests/test_frameworks/test_fastai.py index a806cb4c..59fc2440 100644 --- a/tests/test_frameworks/test_fastai.py +++ b/tests/test_frameworks/test_fastai.py @@ -15,10 +15,8 @@ from dvclive.fastai import DVCLiveCallback from dvclive.plots.metric import Metric -# pylint: disable=redefined-outer-name, unused-argument - -@pytest.fixture +@pytest.fixture() def data_loader(): from pandas import DataFrame @@ -28,7 +26,7 @@ def data_loader(): "y": [1, 0, 0, 1, 1, 0, 0, 1], } df = DataFrame(d) - xor_loader = TabularDataLoaders.from_df( + return TabularDataLoaders.from_df( df, valid_idx=[4, 5, 6, 7], batch_size=2, @@ -36,7 +34,6 @@ def data_loader(): procs=[Categorify, Normalize], y_names="y", ) - return xor_loader def test_fastai_callback(tmp_dir, data_loader, mocker): diff --git a/tests/test_frameworks/test_huggingface.py b/tests/test_frameworks/test_huggingface.py index b723e197..54df32cb 100644 --- a/tests/test_frameworks/test_huggingface.py +++ b/tests/test_frameworks/test_huggingface.py @@ -11,8 +11,6 @@ from dvclive.plots.metric import Metric from dvclive.utils import parse_metrics -# pylint: disable=redefined-outer-name, unused-argument, no-value-for-parameter - def compute_metrics(eval_preds): """https://github.com/iterative/dvclive/pull/321#issuecomment-1266916039""" @@ -74,18 +72,18 @@ def forward(self, input_x, labels=None, **kwargs): return (loss, y, y) if self.double_output else (loss, y) -@pytest.fixture +@pytest.fixture() def data(): return RegressionDataset(), RegressionDataset() -@pytest.fixture +@pytest.fixture() def model(): config = RegressionModelConfig() return RegressionPreTrainedModel(config) -@pytest.fixture +@pytest.fixture() def args(): return TrainingArguments( "foo", diff --git a/tests/test_frameworks/test_keras.py b/tests/test_frameworks/test_keras.py index cb299789..fd40bee6 100644 --- a/tests/test_frameworks/test_keras.py +++ b/tests/test_frameworks/test_keras.py @@ -7,10 +7,8 @@ from dvclive.plots.metric import Metric from dvclive.utils import parse_metrics -# pylint: disable=unused-argument, no-name-in-module, redefined-outer-name - -@pytest.fixture +@pytest.fixture() def xor_model(): import numpy as np from tensorflow.python.keras import Sequential @@ -30,10 +28,10 @@ def make(): return model, x, y - yield make + return make -def test_keras_callback(tmp_dir, xor_model, capture_wrap, mocker): +def test_keras_callback(tmp_dir, xor_model, mocker): model, x, y = xor_model() callback = DVCLiveCallback() @@ -64,8 +62,8 @@ def test_keras_callback_pass_logger(): assert DVCLiveCallback(live=logger).live is logger -@pytest.mark.parametrize("save_weights_only", (True, False)) -def test_keras_model_file(tmp_dir, xor_model, mocker, save_weights_only, capture_wrap): +@pytest.mark.parametrize("save_weights_only", [True, False]) +def test_keras_model_file(tmp_dir, xor_model, mocker, save_weights_only): model, x, y = xor_model() save = mocker.spy(model, "save") save_weights = mocker.spy(model, "save_weights") @@ -86,10 +84,8 @@ def test_keras_model_file(tmp_dir, xor_model, mocker, save_weights_only, capture log_artifact.assert_called_with(live_callback.model_file) -@pytest.mark.parametrize("save_weights_only", (True, False)) -def test_keras_load_model_on_resume( - tmp_dir, xor_model, mocker, save_weights_only, capture_wrap -): +@pytest.mark.parametrize("save_weights_only", [True, False]) +def test_keras_load_model_on_resume(tmp_dir, xor_model, mocker, save_weights_only): import dvclive.keras model, x, y = xor_model() @@ -120,7 +116,7 @@ def test_keras_load_model_on_resume( assert load_weights.call_count == save_weights_only -def test_keras_no_resume_skip_load(tmp_dir, xor_model, mocker, capture_wrap): +def test_keras_no_resume_skip_load(tmp_dir, xor_model, mocker): model, x, y = xor_model() model.save_weights("model.h5") @@ -144,9 +140,7 @@ def test_keras_no_resume_skip_load(tmp_dir, xor_model, mocker, capture_wrap): assert load_weights.call_count == 0 -def test_keras_no_existing_model_file_skip_load( - tmp_dir, xor_model, mocker, capture_wrap -): +def test_keras_no_existing_model_file_skip_load(tmp_dir, xor_model, mocker): model, x, y = xor_model() load_weights = mocker.spy(model, "load_weights") @@ -168,7 +162,7 @@ def test_keras_no_existing_model_file_skip_load( assert load_weights.call_count == 0 -def test_keras_None_model_file_skip_load(tmp_dir, xor_model, mocker, capture_wrap): +def test_keras_none_model_file_skip_load(tmp_dir, xor_model, mocker): model, x, y = xor_model() model.save_weights("model.h5") diff --git a/tests/test_frameworks/test_lgbm.py b/tests/test_frameworks/test_lgbm.py index 0573f57d..5389d3d9 100644 --- a/tests/test_frameworks/test_lgbm.py +++ b/tests/test_frameworks/test_lgbm.py @@ -12,15 +12,13 @@ from dvclive.lgbm import DVCLiveCallback from dvclive.utils import parse_metrics -# pylint: disable=redefined-outer-name, unused-argument - -@pytest.fixture +@pytest.fixture() def model_params(): return {"objective": "multiclass", "n_estimators": 5, "seed": 0} -@pytest.fixture +@pytest.fixture() def iris_data(): iris = datasets.load_iris() x = pd.DataFrame(iris["data"], columns=iris["feature_names"]) diff --git a/tests/test_frameworks/test_lightning.py b/tests/test_frameworks/test_lightning.py index 7aa15af4..b0ae4aff 100644 --- a/tests/test_frameworks/test_lightning.py +++ b/tests/test_frameworks/test_lightning.py @@ -4,7 +4,7 @@ from pytorch_lightning import LightningModule from pytorch_lightning.trainer import Trainer from torch import nn -from torch.nn import functional as F +from torch.nn import functional as F # noqa: N812 from torch.optim import Adam from torch.utils.data import DataLoader, Dataset @@ -13,8 +13,6 @@ from dvclive.serialize import load_yaml from dvclive.utils import parse_metrics -# pylint: disable=redefined-outer-name, unused-argument - class XORDataset(Dataset): def __init__(self, *args, **kwargs): @@ -51,12 +49,10 @@ def forward(self, *args, **kwargs): def train_loader(self): dataset = XORDataset() - loader = DataLoader(dataset, batch_size=1) - return loader + return DataLoader(dataset, batch_size=1) def train_dataloader(self): - loader = self.train_loader() - return loader + return self.train_loader() def training_step(self, *args, **kwargs): batch = args[0] @@ -184,12 +180,10 @@ def test_lightning_steps(tmp_dir, mocker): class ValLitXOR(LitXOR): def val_loader(self): dataset = XORDataset() - loader = DataLoader(dataset, batch_size=1) - return loader + return DataLoader(dataset, batch_size=1) def val_dataloader(self): - loader = self.val_loader() - return loader + return self.val_loader() def training_step(self, *args, **kwargs): batch = args[0] diff --git a/tests/test_frameworks/test_optuna.py b/tests/test_frameworks/test_optuna.py index ef1d4b80..a8cdaee8 100644 --- a/tests/test_frameworks/test_optuna.py +++ b/tests/test_frameworks/test_optuna.py @@ -1,4 +1,3 @@ -# pylint: disable=protected-access, unused-argument import optuna from dvclive.optuna import DVCLiveCallback diff --git a/tests/test_frameworks/test_xgboost.py b/tests/test_frameworks/test_xgboost.py index 4264f1dd..d5760af2 100644 --- a/tests/test_frameworks/test_xgboost.py +++ b/tests/test_frameworks/test_xgboost.py @@ -10,15 +10,13 @@ from dvclive.utils import parse_metrics from dvclive.xgb import DVCLiveCallback -# pylint: disable=redefined-outer-name, unused-argument - -@pytest.fixture +@pytest.fixture() def train_params(): return {"objective": "multi:softmax", "num_class": 3, "seed": 0} -@pytest.fixture +@pytest.fixture() def iris_data(): iris = datasets.load_iris() x = pd.DataFrame(iris["data"], columns=iris["feature_names"]) diff --git a/tests/test_log_artifact.py b/tests/test_log_artifact.py index 624ea33e..d3b35a05 100644 --- a/tests/test_log_artifact.py +++ b/tests/test_log_artifact.py @@ -1,4 +1,3 @@ -# pylint: disable=unused-argument,protected-access from dvclive import Live diff --git a/tests/test_main.py b/tests/test_main.py index f074b8c2..3342dc2a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,3 @@ -# pylint: disable=protected-access -# pylint: disable=unused-argument import json import os from io import StringIO @@ -44,7 +42,7 @@ def test_logging_no_step(tmp_dir): @pytest.mark.parametrize( - "param_name,param_value", + ("param_name", "param_value"), [ ("param_string", "value"), ("param_int", 42), @@ -93,7 +91,7 @@ def test_log_params(tmp_dir): assert s == params -@pytest.mark.parametrize("resume", (False, True)) +@pytest.mark.parametrize("resume", [False, True]) def test_log_params_resume(tmp_dir, resume): dvclive = Live(resume=resume) dvclive.log_param("param", 42) @@ -193,7 +191,7 @@ def test_cleanup_params(tmp_dir): @pytest.mark.parametrize( - "resume, steps, metrics", + ("resume", "steps", "metrics"), [(True, [0, 1, 2, 3], [0.9, 0.8, 0.7, 0.6]), (False, [0, 1], [0.7, 0.6])], ) def test_continue(tmp_dir, resume, steps, metrics): @@ -351,7 +349,7 @@ def test_make_summary_without_calling_log(tmp_dir): assert not log_file.exists() -@pytest.mark.parametrize("timestamp", (True, False)) +@pytest.mark.parametrize("timestamp", [True, False]) def test_log_metric_timestamp(tmp_dir, timestamp): live = Live() live.log_metric("foo", 1.0, timestamp=timestamp) @@ -440,7 +438,7 @@ def checkpoint_never_exists(path): assert not native_exists(os.path.join(".dvc", "tmp", env.DVC_CHECKPOINT)) -@pytest.mark.vscode +@pytest.mark.vscode() @pytest.mark.parametrize("dvc_root", [True, False]) def test_vscode_dvclive_only_signal_file(tmp_dir, dvc_root, mocker): signal_file = os.path.join(tmp_dir, ".dvc", "tmp", "exps", "run", "DVCLIVE_ONLY") diff --git a/tests/test_report.py b/tests/test_report.py index 81897421..1de98c6f 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -1,4 +1,3 @@ -# pylint: disable=unused-argument,protected-access import os import pytest @@ -7,6 +6,7 @@ from dvclive import Live from dvclive.env import DVCLIVE_OPEN +from dvclive.error import InvalidReportModeError from dvclive.plots import Image as LiveImage from dvclive.plots import Metric from dvclive.plots.sklearn import ConfusionMatrix, Roc, SKLearnPlot @@ -89,7 +89,7 @@ def test_report_init(monkeypatch, mocker): live = Live(report=report) assert live._report_mode == report - with pytest.raises(ValueError): + with pytest.raises(InvalidReportModeError, match="Got foo instead."): Live(report="foo") @@ -108,7 +108,7 @@ def test_make_report(tmp_dir, mode): last_report = current_report -@pytest.mark.vscode +@pytest.mark.vscode() def test_make_report_open(tmp_dir, mocker, monkeypatch): mocked_open = mocker.patch("webbrowser.open") live = Live() diff --git a/tests/test_studio.py b/tests/test_studio.py index 805cb013..0dcefcc7 100644 --- a/tests/test_studio.py +++ b/tests/test_studio.py @@ -1,4 +1,3 @@ -# pylint: disable=protected-access,redefined-outer-name,unused-argument from pathlib import Path import pytest @@ -175,7 +174,7 @@ def test_post_to_studio_end_only_once(tmp_dir, mocked_dvc_repo, mocked_studio_po assert mocked_post.call_count == 3 -@pytest.mark.studio +@pytest.mark.studio() def test_post_to_studio_skip_on_env_var( tmp_dir, mocked_dvc_repo, mocked_studio_post, monkeypatch ): @@ -190,7 +189,7 @@ def test_post_to_studio_skip_on_env_var( assert mocked_post.call_count == 1 -@pytest.mark.studio +@pytest.mark.studio() def test_post_to_studio_skip_if_no_token( tmp_dir, mocker, @@ -274,7 +273,7 @@ def test_post_to_studio_shorten_names(tmp_dir, mocked_dvc_repo, mocked_studio_po ) -@pytest.mark.studio +@pytest.mark.studio() def test_post_to_studio_inside_dvc_exp( tmp_dir, mocker, monkeypatch, mocked_studio_post ): @@ -290,7 +289,7 @@ def test_post_to_studio_inside_dvc_exp( assert mocked_post.call_count == 1 -@pytest.mark.studio +@pytest.mark.studio() def test_post_to_studio_inside_subdir( tmp_dir, dvc_repo, mocker, monkeypatch, mocked_studio_post ): @@ -331,7 +330,7 @@ def test_post_to_studio_inside_subdir( ) -@pytest.mark.studio +@pytest.mark.studio() def test_post_to_studio_inside_subdir_dvc_exp( tmp_dir, dvc_repo, monkeypatch, mocked_studio_post ): diff --git a/tests/test_utils.py b/tests/test_utils.py index fc8fdd19..cb452125 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,7 +4,7 @@ @pytest.mark.parametrize( - "framework,logged,standardized", + ("framework", "logged", "standardized"), [ ("dvclive.lightning", "epoch", "epoch"), ("dvclive.lightning", "train_loss", "train/loss"),