From cdc3ed943995e690cd73568914b79284685ca198 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:08:52 +0200 Subject: [PATCH] Updating pre-commit and running pylint/pyright --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 4 +- .pre-commit-config.yaml | 20 ++-- .readthedocs.yaml | 2 +- awpy/cli.py | 13 +- awpy/data/map_data.py | 26 +++- awpy/demo.py | 8 +- awpy/parsers/clock.py | 4 +- awpy/parsers/events.py | 2 +- awpy/parsers/rounds.py | 4 +- awpy/parsers/ticks.py | 2 +- awpy/stats/adr.py | 5 +- awpy/stats/kast.py | 2 +- awpy/vis/plot.py | 16 ++- awpy/vis/utils.py | 2 +- pyproject.toml | 218 +++++++++++++++++----------------- 16 files changed, 184 insertions(+), 146 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 456de85b4..43e09465a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,7 @@ jobs: - name: Install Poetry uses: abatilo/actions-poetry@v2 - + - name: Setup a local virtual environment for poetry shell: bash run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b37ffe49..9bbee97a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - name: Install awpy run: | poetry install --no-interaction - + - name: Publish to PyPI run: | - poetry publish --build --username __token__ --password ${{ secrets.PYPI_API_TOKEN }} -vvv \ No newline at end of file + poetry publish --build --username __token__ --password ${{ secrets.PYPI_API_TOKEN }} -vvv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 840ff0356..9b0e25aa4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,11 @@ exclude: docs/ repos: + # - repo: https://github.com/JanEricNitschke/pymend + # rev: "v1.0.10" + # hooks: + # - id: pymend + # language: python + # args: ["--write", "--check"] - repo: 'https://github.com/pre-commit/pre-commit-hooks' rev: v4.4.0 hooks: @@ -25,18 +31,14 @@ repos: language: python - id: check-builtin-literals language: python - - repo: 'https://github.com/charliermarsh/ruff-pre-commit' - rev: v0.0.291 + - repo: "https://github.com/charliermarsh/ruff-pre-commit" + rev: v0.4.9 hooks: - id: ruff args: - - '--fix' - - '--exit-non-zero-on-fix' - - repo: 'https://github.com/psf/black' - rev: 23.9.1 - hooks: - - id: black - language: python + - "--fix" + - "--exit-non-zero-on-fix" + - id: ruff-format - repo: https://github.com/crate-ci/typos rev: v1.16.13 hooks: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 02fa1f967..10d6c088d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,4 +19,4 @@ build: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install sphinx: - configuration: docs/conf.py \ No newline at end of file + configuration: docs/conf.py diff --git a/awpy/cli.py b/awpy/cli.py index e714d025e..942b57c26 100644 --- a/awpy/cli.py +++ b/awpy/cli.py @@ -28,8 +28,12 @@ def get(resource_type: Literal["usd"], resource_name: str) -> None: @awpy.command(help="Parse a Counter-Strike 2 demo file.") -@click.argument("demo", type=click.Path(exists=True)) -@click.option("--outpath", type=click.Path(), help="Path to save the compressed demo.") +@click.argument("demo_path", type=click.Path(exists=True, path_type=Path)) +@click.option( + "--outpath", + type=click.Path(path_type=Path), + help="Path to save the compressed demo.", +) @click.option("--verbose", is_flag=True, default=False, help="Enable verbose mode.") @click.option("--noticks", is_flag=True, default=False, help="Disable tick parsing.") @click.option( @@ -45,7 +49,7 @@ def get(resource_type: Literal["usd"], resource_name: str) -> None: "--other-props", multiple=True, help="List of other properties to include." ) def parse( - demo: Path, + demo_path: Path, *, outpath: Optional[Path] = None, verbose: bool = False, @@ -54,8 +58,7 @@ def parse( player_props: Optional[tuple[str]] = None, other_props: Optional[tuple[str]] = None, ) -> None: - """Parse a file given its path.""" - demo_path = Path(demo) # Pathify + """Parse a file given its path.""" # Pathify demo = Demo( path=demo_path, verbose=verbose, diff --git a/awpy/data/map_data.py b/awpy/data/map_data.py index 6fcffbda6..76bbdfd5c 100644 --- a/awpy/data/map_data.py +++ b/awpy/data/map_data.py @@ -1,7 +1,31 @@ """Dictionary that holds map data for Counter-Strike 2.""" +from typing import Optional, TypedDict, Union + +Number = Union[int, float] + + +class Selection(TypedDict): + """Structure of selections data.""" + + name: str + altitude_max: Number + altitude_min: Number + + +class MapData(TypedDict): + """Structure of map data.""" + + pos_x: Number + pos_y: Number + scale: Number + rotate: Optional[Number] + zoom: Optional[Number] + selections: list[Selection] + + # pos_x is upper left world coordinate -MAP_DATA = { +MAP_DATA: dict[str, MapData] = { "ar_baggage": { "pos_x": -1316, "pos_y": 1288, diff --git a/awpy/demo.py b/awpy/demo.py index effb500cd..15e4eede7 100644 --- a/awpy/demo.py +++ b/awpy/demo.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Optional -from demoparser2 import DemoParser # pylint: disable=E0611 +from demoparser2 import DemoParser from loguru import logger from awpy.parsers.clock import parse_times @@ -62,7 +62,7 @@ ] -class Demo: +class Demo: # pylint: disable=R0902 """Class to store a demo's data. Called with `Demo(file="...")`.""" def __init__( @@ -265,7 +265,7 @@ def compress(self, outpath: Optional[Path] = None) -> None: Args: outpath (Path): Path to save the zip file. Defaults to cwd. """ - outpath = Path.cwd() if outpath is None else Path(outpath) + outpath = Path.cwd() if outpath is None else outpath zip_name = outpath / Path(self.path.stem + ".zip") with ( @@ -284,6 +284,8 @@ def compress(self, outpath: Optional[Path] = None) -> None: ("rounds", self.rounds), ("grenades", self.grenades), ]: + if df is None: + continue df_filename = os.path.join(tmpdirname, f"{df_name}.data") df.to_parquet(df_filename, index=False) zipf.write(df_filename, f"{df_name}.data") diff --git a/awpy/parsers/clock.py b/awpy/parsers/clock.py index a38e4d382..7b641fa46 100644 --- a/awpy/parsers/clock.py +++ b/awpy/parsers/clock.py @@ -62,8 +62,8 @@ def _find_clock_time(row: pd.Series) -> str: "bomb": row["ticks_since_bomb_plant"], } # Filter out NA values and find the key with the minimum value - min_key = min((k for k in times if pd.notna(times[k])), key=lambda k: times[k]) - return parse_clock(times[min_key], min_key) + min_key = min((k for k in times if pd.notna(times[k])), key=lambda k: times[k]) # pylint: disable=C0206 + return parse_clock(times[min_key], min_key) # pyright: ignore[reportArgumentType] def parse_times( diff --git a/awpy/parsers/events.py b/awpy/parsers/events.py index 0b3e82bd8..52da3146a 100644 --- a/awpy/parsers/events.py +++ b/awpy/parsers/events.py @@ -2,7 +2,7 @@ import numpy as np import pandas as pd -from demoparser2 import DemoParser # pylint: disable=E0611 +from demoparser2 import DemoParser from loguru import logger from awpy.converters import ( diff --git a/awpy/parsers/rounds.py b/awpy/parsers/rounds.py index 651cbb95c..1a0ad8b7d 100644 --- a/awpy/parsers/rounds.py +++ b/awpy/parsers/rounds.py @@ -4,7 +4,7 @@ import numpy as np import pandas as pd -from demoparser2 import DemoParser # pylint: disable=E0611 +from demoparser2 import DemoParser def _find_bomb_plant_tick(row: pd.Series, bomb_ticks: pd.Series) -> Union[int, float]: @@ -143,7 +143,7 @@ def parse_rounds(parser: DemoParser, events: dict[str, pd.DataFrame]) -> pd.Data # Find the bomb plant ticks bomb_planted = events.get("bomb_planted") - if bomb_planted.shape[0] == 0: + if not bomb_planted or bomb_planted.shape[0] == 0: return rounds_df rounds_df["bomb_plant"] = rounds_df.apply( diff --git a/awpy/parsers/ticks.py b/awpy/parsers/ticks.py index ad7e43523..ca53aead2 100644 --- a/awpy/parsers/ticks.py +++ b/awpy/parsers/ticks.py @@ -1,7 +1,7 @@ """Module for tick parsing functions.""" import pandas as pd -from demoparser2 import DemoParser # pylint: disable=E0611 +from demoparser2 import DemoParser from awpy.parsers.utils import parse_col_types diff --git a/awpy/stats/adr.py b/awpy/stats/adr.py index a6c7bf97b..f9c812074 100644 --- a/awpy/stats/adr.py +++ b/awpy/stats/adr.py @@ -8,8 +8,9 @@ def adr( demo: Demo, - team_dmg: bool = False, # noqa: FBT001, FBT002 - self_dmg: bool = True, # noqa: FBT001, FBT002 + *, + team_dmg: bool = False, + self_dmg: bool = True, ) -> pd.DataFrame: """Calculates Average Damage Per Round. Does not include team damage. diff --git a/awpy/stats/kast.py b/awpy/stats/kast.py index cf849e228..69b7b7ffb 100644 --- a/awpy/stats/kast.py +++ b/awpy/stats/kast.py @@ -43,7 +43,7 @@ def calculate_trades(kills: pd.DataFrame, trade_ticks: int = 128 * 5) -> pd.Data return kills -def kast(demo: Demo, trade_ticks: int = 128 * 5) -> pd.DataFrame: +def kast(demo: Demo, trade_ticks: int = 128 * 5) -> pd.DataFrame: # pylint: disable=R0914 """Calculates Kill-Assist-Survival-Trade %. Args: diff --git a/awpy/vis/plot.py b/awpy/vis/plot.py index 81b4d3227..e6ca24bde 100644 --- a/awpy/vis/plot.py +++ b/awpy/vis/plot.py @@ -1,7 +1,7 @@ """Module for plotting Counter-Strike data.""" import importlib.resources -from typing import Optional +from typing import Optional, Union, overload import matplotlib.image as mpimg import matplotlib.pyplot as plt @@ -9,7 +9,17 @@ from matplotlib.figure import Figure -def plot_map(map_name: str, *, lower: Optional[bool] = None) -> tuple[Figure, Axes]: +@overload +def plot_map(map_name: str, *, lower: None = ...) -> tuple[Figure, list[Axes]]: ... + + +@overload +def plot_map(map_name: str, *, lower: bool = ...) -> tuple[Figure, Axes]: ... + + +def plot_map( + map_name: str, *, lower: Optional[bool] = None +) -> tuple[Figure, Union[Axes, list[Axes]]]: """Plot a Counter-Strike map. Args: @@ -60,7 +70,7 @@ def plot_upper_and_lower(map_name: str) -> tuple[Figure, list[Axes]]: map_names = [map_name, f"{map_name}_lower"] figure, axes = plt.subplots(1, 2) # , figsize=(2 * 5, 5) - for ax, map_layer_name in zip(axes, map_names, strict=True): + for ax, map_layer_name in zip(axes, map_names): with importlib.resources.path( "awpy.data.maps", f"{map_layer_name}.png" ) as map_img_path: diff --git a/awpy/vis/utils.py b/awpy/vis/utils.py index 12e5e2b06..28ab144ef 100644 --- a/awpy/vis/utils.py +++ b/awpy/vis/utils.py @@ -22,7 +22,7 @@ def position_transform_axis( Raises: ValueError: Raises a ValueError if axis not 'x' or 'y' """ - axis = axis.lower() + axis = axis.lower() # pyright: ignore[reportAssignmentType] if axis not in ["x", "y"]: msg = f"'axis' has to be 'x' or 'y', not {axis}" raise ValueError(msg) diff --git a/pyproject.toml b/pyproject.toml index 3d82d69d0..73c2a3c71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] @@ -9,7 +9,7 @@ description = "Counter-Strike 2 demo parsing, analysis and visualization" readme = "README.md" authors = [ "Peter Xenopoulos ", - "Jan-Eric Nitschke " + "Jan-Eric Nitschke ", ] license = "MIT" keywords = ["counter-strike 2", "counter-strike", "csgo", "esports", "sports-analytics"] @@ -22,34 +22,34 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Topic :: Games/Entertainment", "Topic :: Games/Entertainment :: First Person Shooters", - "Topic :: Scientific/Engineering :: Information Analysis" + "Topic :: Scientific/Engineering :: Information Analysis", ] homepage = "https://awpycs.com" repository = "https://github.com/pnxenopoulos/awpy" [tool.poetry.dependencies] -python = ">=3.9" -click = ">=8.1.7" -loguru = ">=0.7.2" -matplotlib = ">=3.9.0" -numpy = "^1.26.4" -pandas = ">=2.2.2" -setuptools = ">=70.1.0" +python = ">=3.9" +click = ">=8.1.7" +loguru = ">=0.7.2" +matplotlib = ">=3.9.0" +numpy = "^1.26.4" +pandas = ">=2.2.2" +setuptools = ">=70.1.0" demoparser2 = ">=0.26.2" -tqdm = "^4.66.4" +tqdm = "^4.66.4" [tool.poetry.dev-dependencies] -black = { version = "^24.4.2", extras = ["jupyter"] } -pytest = "^8.2.2" -requests = "^2.32.3" -ruff = "^0.4.10" -pyright = "^1.1.368" -pylint = "^3.2.3" -coverage = { version = "^7.5.3", extras = ["toml"] } -sphinx = "^7.3.7" +black = { version = "^24.4.2", extras = ["jupyter"] } +pytest = "^8.2.2" +requests = "^2.32.3" +ruff = "^0.4.10" +pyright = "^1.1.368" +pylint = "^3.2.3" +coverage = { version = "^7.5.3", extras = ["toml"] } +sphinx = "^7.3.7" sphinx-rtd-theme = "^2.0.0" -nbsphinx = "^0.9.3" -ipykernel = "^6.29.4" +nbsphinx = "^0.9.3" +ipykernel = "^6.29.4" [tool.poetry.scripts] awpy = "awpy.cli:awpy" @@ -62,6 +62,14 @@ source = ["awpy"] [tool.pytest.ini_options] testpaths = ["tests"] +[tool.pymend] +extend-exclude = "docs/|tests/|setup.py" +output-style = "google" +input-style = "google" +check = true +force-params-min-n-params = 2 +force-meta-min-func-length = 3 + # Setuptools [tool.setuptools] include-package-data = true @@ -92,125 +100,113 @@ exclude = [ "dist", "node_modules", "venv", - "docs" + "docs", ] -select = [ - "E", - "F", - "B", - "W", - "I", - "N", - "D", - "UP", - "YTT", - "ANN", - "S", - "BLE", - "FBT", - "A", - "C4", - "DTZ", - "T10", - "EXE", - "ISC", - "ICN", - "G", - "INP", - "PIE", - "PYI", - "PT", - "Q", - "RSE", - "RET", - "SLF", - "SIM", - "TID", - "TCH", - "ARG", - "ERA", - "PD", - "PGH", - "PLC", - "PLE", - "PLR", - "PLW", - "TRY", - "NPY", - "RUF", - "EM" +line-length = 88 +target-version = "py39" + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + # Formatter + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", + # Others + "D208", + "ANN101", + "T20", + "PTH", + "TRY003", + "BLE001", + "PLR2004", + "UP007", + "ISC001", + "FA100", ] -ignore = ["D208", "ANN101", "T20", "PTH", "TRY003", "BLE001", "PLR2004", "UP007", "ISC001"] dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -line-length = 88 -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "google" -[tool.ruff.pylint] +[tool.ruff.lint.pylint] max-args = 17 -[tool.ruff.per-file-ignores] -"__init__.py" = ["E402", "F401"] -"tests/test_*.py" = ["ANN201", "S101", "SLF001", "PLR2004"] +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402", "F401"] +"tests/test_*.py" = ["ANN201", "S101", "SLF001", "PLR2004"] # Pyright config [tool.pyright] -include = ["awpy"] -exclude = ["tests"] -strictListInference = true -strictDictionaryInference = true -strictSetInference = true -useLibraryCodeForTypes = false -reportPropertyTypeMismatch = "error" -reportFunctionMemberAccess = "warning" -reportMissingTypeStubs = "none" -reportUntypedFunctionDecorator = "error" -reportUntypedClassDecorator = "error" -reportUntypedBaseClass = "error" -reportUntypedNamedTuple = "error" -reportPrivateUsage = "error" -reportConstantRedefinition = "error" -reportOverlappingOverload = "error" -reportMissingParameterType = "warning" -reportUnnecessaryIsInstance = "none" -reportUnnecessaryCast = "error" -reportUnnecessaryComparison = "error" -reportUnnecessaryContains = "error" -reportAssertAlwaysTrue = "error" +pythonVersion = "3.9" +include = ["awpy"] +exclude = ["tests"] +# typeCheckingMode = "strict" +strictListInference = true +strictDictionaryInference = true +strictSetInference = true +useLibraryCodeForTypes = false +reportPropertyTypeMismatch = "error" +reportFunctionMemberAccess = "warning" +reportMissingTypeStubs = "none" +reportUntypedFunctionDecorator = "error" +reportUntypedClassDecorator = "error" +reportUntypedBaseClass = "error" +reportUntypedNamedTuple = "error" +reportPrivateUsage = "error" +reportConstantRedefinition = "error" +reportOverlappingOverload = "error" +reportMissingParameterType = "warning" +reportUnnecessaryIsInstance = "none" +reportUnnecessaryCast = "error" +reportUnnecessaryComparison = "error" +reportUnnecessaryContains = "error" +reportAssertAlwaysTrue = "error" reportUnnecessaryTypeIgnoreComment = "error" -reportImplicitOverride = "none" -reportShadowedImports = "error" +reportImplicitOverride = "none" +reportShadowedImports = "error" # Pylint config [tool.pylint.main] -fail-under = 9.85 +fail-under = 10.0 +extension-pkg-allow-list = ["demoparser2"] [tool.pylint.basic] -good-names = ["i", "j", "k", "ex", "Run", "_", "x", "y", "z", "e", "PlayerPosition2D"] +good-names = ["i", "j", "k", "ex", "Run", "_", "x", "y", "z", "e", "PlayerPosition2D"] include-naming-hint = true [tool.pylint.design] -max-args = 15 -max-attributes = 8 -max-bool-expr = 5 -max-branches = 12 -max-locals = 20 +max-args = 15 +max-attributes = 8 +max-bool-expr = 5 +max-branches = 12 +max-locals = 20 max-public-methods = 30 -max-returns = 6 -max-statements = 50 +max-returns = 6 +max-statements = 50 min-public-methods = 1 [tool.pylint.exceptions] overgeneral-exceptions = ["builtins.BaseException"] [tool.pylint.format] -max-line-length = 88 +max-line-length = 88 max-module-lines = 2000 [tool.pylint."messages control"] confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] -disable = ["unnecessary-ellipsis"] +disable = ["unnecessary-ellipsis"] [tool.pylint.miscellaneous] notes = ["FIXME", "XXX", "TODO"] @@ -219,7 +215,7 @@ notes = ["FIXME", "XXX", "TODO"] disable = ["R0801"] [tool.pylint.refactoring] -max-nested-blocks = 5 +max-nested-blocks = 5 never-returning-functions = ["sys.exit", "argparse.parse_error"] [tool.pylint.reports] @@ -229,4 +225,4 @@ evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refact min-similarity-lines = 4 [tool.pylint.spelling] -max-spelling-suggestions = 4 \ No newline at end of file +max-spelling-suggestions = 4