Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

Commit

Permalink
Update lint configuration (#199)
Browse files Browse the repository at this point in the history
* Run poetry update

* Fix poetry dev dependency declarations

* Make poetry2setup real dependency instead of using git

* Add ruff

* Configure black line length

* Automatically fix lints

* Add missing timezone when getting time

* Remove flake8 and isort

* Remove flake8 configuration

* Update pre-commit hook configuration

* Update CI to run ruff instead of flake8
  • Loading branch information
TimJentzsch authored May 17, 2023
1 parent 59fab01 commit a589c98
Show file tree
Hide file tree
Showing 23 changed files with 627 additions and 980 deletions.
8 changes: 0 additions & 8 deletions .flake8

This file was deleted.

18 changes: 11 additions & 7 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ on: [pull_request]

jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Checkout current branch
uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v4
- name: Set up Python 3.10.x
uses: actions/setup-python@v4
with:
python-version: '3.10.x'
- name: Install latest pyflakes and pycodestyle version
run: |
pip install --upgrade pip
pip install flake8 pyflakes pycodestyle
python-version: 3.10.x
- uses: snok/install-poetry@v1
with:
virtualenvs-create: true
- name: Install Dependencies
run: poetry install
- name: Run Static Analysis
run: flake8
run: |
poetry run ruff .
23 changes: 4 additions & 19 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
repos:
- repo: https://github.com/asottile/seed-isort-config
rev: v2.2.0
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.10.1
hooks:
- id: isort
- repo: https://github.com/ambv/black
rev: 22.6.0
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 5.0.1
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.265
hooks:
- id: flake8
additional_dependencies: [
"flake8-annotations==2.9.0",
"flake8-black==0.3.3",
"flake8-docstrings==1.6.0",
#"flake8-isort==4.1.2", # flake8-isort doesn't support flake8 5 yet, uncomment this when it does.
"flake8-variables-names==0.0.5"
]
- id: ruff
51 changes: 51 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
select = [
"E", # pycodestyle (error)
"W", # pycodestyle (warning)
"D", # pydocstyle
"I", # isort
"ANN", # flake8-annotations
"DTZ", # flake8-datetimez
]
ignore = [
"ANN101", # missing-type-self
"ANN401", # any-type
"D100", # undocumented-public-module
"D101", # undocumented-public-class
"D105", # undocumented-magic-method
"D106", # undocumented-public-nested-class
"D203", # one-blank-line-before-class
"D213", # multi-line-summary-second-line
]

exclude = [
"build",
"dist",
]

line-length = 100 # Same as black

[per-file-ignores]
"test_*" = [
"D", # Disable documentation lints for test files
"DTZ", # Disable timezone lints for test files
]

[isort]
known-third-party = [
"asyncpraw",
"asyncprawcore",
"blossom_wrapper",
"click",
"dateutil",
"discord",
"discord_slash",
"matplotlib",
"pandas",
"pytest",
"pytz",
"requests",
"seaborn",
"shiv",
"toml",
"yaml"
]
4 changes: 1 addition & 3 deletions buttercup/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ def __init__(self, command_prefix: str, **kwargs: Any) -> None:
if archive:
# if archive is none, we're not in the zipfile and are probably
# in development mode right now.
self.config_path = str(
pathlib.Path(archive.filename).parent / "config.toml"
)
self.config_path = str(pathlib.Path(archive.filename).parent / "config.toml")
else:
self.config_path = "../config.toml"

Expand Down
14 changes: 5 additions & 9 deletions buttercup/cogs/find.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import Dict, Optional, Tuple

import pytz
from blossom_wrapper import BlossomAPI
from discord import Color, Embed
from discord.ext.commands import Cog
Expand Down Expand Up @@ -28,8 +29,7 @@ def limit_str(text: str, limit: Optional[int] = None) -> str:


def get_clean_transcription(data: Dict) -> Optional[str]:
"""
Get the content of the transcription, without header and footer.
"""Get the content of the transcription, without header and footer.
Because there can be an OCR transcription as well, then we'll
try and return the human one but will fall through to the OCR
Expand All @@ -53,9 +53,7 @@ def get_clean_transcription(data: Dict) -> Optional[str]:
def get_color_and_status(data: Dict) -> Tuple[str, str]:
"""Get the color and status for the embed."""
author = data.get("author", None)
author_link = (
i18n["reddit"]["user_named_link"].format(author["username"]) if author else None
)
author_link = i18n["reddit"]["user_named_link"].format(author["username"]) if author else None

if data["submission"].get("completed_by"):
# The post has been completed
Expand Down Expand Up @@ -157,7 +155,7 @@ def __init__(self, bot: ButtercupBot, blossom_api: BlossomAPI) -> None:
)
async def _find(self, ctx: SlashContext, reddit_url: str) -> None:
"""Find the post with the given URL."""
start = datetime.now()
start = datetime.now(tz=pytz.UTC)

# Send a first message to show that the bot is responsive.
# We will edit this message later with the actual content.
Expand All @@ -170,9 +168,7 @@ async def _find(self, ctx: SlashContext, reddit_url: str) -> None:
data = find_response.json()

await msg.edit(
content=i18n["find"]["embed_message"].format(
duration=get_duration_str(start)
),
content=i18n["find"]["embed_message"].format(duration=get_duration_str(start)),
embed=to_embed(data),
)

Expand Down
40 changes: 10 additions & 30 deletions buttercup/cogs/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,57 +31,37 @@ async def on_command_completion(self, ctx: commands.Context) -> None:
logger.info("Command Completed", ctx)

@commands.Cog.listener()
async def on_slash_command_error(
self, ctx: commands.Context, error: DiscordException
) -> None:
async def on_slash_command_error(self, ctx: commands.Context, error: DiscordException) -> None:
"""Log that a command has errored and provide the user with feedback."""
trace = "".join(
traceback.format_exception(type(error), error, error.__traceback__)
)
trace = "".join(traceback.format_exception(type(error), error, error.__traceback__))

if isinstance(error, NoUsernameException):
logger.warning("Command executed without providing a username.", ctx)
await ctx.send(i18n["handlers"]["no_username"])
elif isinstance(error, UserNotFoundException):
logger.warning(f"User '{error.username}' not found.", ctx)
await ctx.send(
i18n["handlers"]["user_not_found"].format(user=error.username)
)
await ctx.send(i18n["handlers"]["user_not_found"].format(user=error.username))
elif isinstance(error, NewUserException):
logger.warning(f"User '{error.username}' hasn't transcribed yet.", ctx)
await ctx.send(i18n["handlers"]["new_user"].format(user=error.username))
elif isinstance(error, TimeParseError):
logger.warning(
f"Command executed with an invalid time string '{error.time_str}'.", ctx
)
await ctx.send(
i18n["handlers"]["invalid_time_str"].format(time_str=error.time_str)
)
logger.warning(f"Command executed with an invalid time string '{error.time_str}'.", ctx)
await ctx.send(i18n["handlers"]["invalid_time_str"].format(time_str=error.time_str))
elif isinstance(error, InvalidArgumentException):
logger.warning(
f"Invalid value '{error.value}' for argument '{error.argument}'.", ctx
)
logger.warning(f"Invalid value '{error.value}' for argument '{error.argument}'.", ctx)
await ctx.send(
i18n["handlers"]["invalid_argument"].format(
argument=error.argument, value=error.value
)
)
elif isinstance(error, BlossomException):
tracker_id = uuid.uuid4()
logger.warning(
f"[{tracker_id}] Blossom Error: {error.status}\n{error.data}", ctx
)
await ctx.send(
i18n["handlers"]["blossom_error"].format(tracker_id=tracker_id)
)
logger.warning(f"[{tracker_id}] Blossom Error: {error.status}\n{error.data}", ctx)
await ctx.send(i18n["handlers"]["blossom_error"].format(tracker_id=tracker_id))
else:
tracker_id = uuid.uuid4()
logger.warning(
f"[{tracker_id}] {type(error).__name__}: {str(error)}\n{trace}", ctx
)
await ctx.send(
i18n["handlers"]["unknown_error"].format(tracker_id=tracker_id)
)
logger.warning(f"[{tracker_id}] {type(error).__name__}: {str(error)}\n{trace}", ctx)
await ctx.send(i18n["handlers"]["unknown_error"].format(tracker_id=tracker_id))


def setup(bot: ButtercupBot) -> None:
Expand Down
28 changes: 9 additions & 19 deletions buttercup/cogs/heatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import matplotlib.pyplot as plt
import pandas as pd
import pytz
import seaborn as sns
from blossom_wrapper import BlossomAPI
from dateutil import parser
Expand Down Expand Up @@ -40,9 +41,7 @@ def create_file_from_heatmap(
# The built in formatting for the heatmap doesn't allow displaying floats as ints
# And we have to use floats because empty entries are NaN
# So we have to manually provide the annotations
annotations = heatmap.apply(
lambda series: series.apply(lambda value: f"{value:0.0f}")
)
annotations = heatmap.apply(lambda series: series.apply(lambda value: f"{value:0.0f}"))

fig, ax = plt.subplots()
fig: plt.Figure
Expand All @@ -61,9 +60,7 @@ def create_file_from_heatmap(

timezone = utc_offset_to_str(utc_offset)

plt.title(
i18n["heatmap"]["plot_title"].format(user=get_username(user, escape=False))
)
plt.title(i18n["heatmap"]["plot_title"].format(user=get_username(user, escape=False)))
plt.xlabel(i18n["heatmap"]["plot_xlabel"].format(timezone=timezone))
plt.ylabel(i18n["heatmap"]["plot_ylabel"])

Expand Down Expand Up @@ -123,9 +120,7 @@ def _create_file_from_activity_map(
# Only annotate the maximum values
max_value = activity_df.max().max()
annotations = activity_df.apply(
lambda series: series.apply(
lambda value: f"{value:0.0f}" if value == max_value else ""
)
lambda series: series.apply(lambda value: f"{value:0.0f}" if value == max_value else "")
)

fig, ax = plt.subplots()
Expand Down Expand Up @@ -155,9 +150,7 @@ def _create_file_from_activity_map(
)

ax.set_title(
i18n["activity"]["plot_title"].format(
user=get_username(user, escape=False), time=time_str
)
i18n["activity"]["plot_title"].format(user=get_username(user, escape=False), time=time_str)
)
# Remove axis labels
ax.set_xlabel(None)
Expand Down Expand Up @@ -209,7 +202,7 @@ async def _heatmap(
before: Optional[str] = None,
) -> None:
"""Generate a heatmap for the given user."""
start = datetime.now()
start = datetime.now(tz=pytz.UTC)

after_time, before_time, time_str = parse_time_constraints(after, before)

Expand Down Expand Up @@ -289,7 +282,7 @@ async def activity_map(
before: Optional[str] = None,
) -> None:
"""Generate a yearly activity heatmap for the given user."""
start = datetime.now()
start = datetime.now(tz=pytz.UTC)

# First parse the end time for the activity map
_, before_time, _ = parse_time_constraints(None, before)
Expand All @@ -304,9 +297,7 @@ async def activity_map(
after_time, _, time_str = parse_time_constraints(after, before)

msg = await ctx.send(
i18n["activity"]["getting_activity"].format(
user=get_initial_username(username, ctx)
)
i18n["activity"]["getting_activity"].format(user=get_initial_username(username, ctx))
)

from_str = after_time.isoformat() if after_time else None
Expand Down Expand Up @@ -341,8 +332,7 @@ async def activity_map(

# All possible weeks, in case some are missing in the data
all_week_indexes = [
_get_week_index((before_time or start) - timedelta(weeks=weeks))
for weeks in range(53)
_get_week_index((before_time or start) - timedelta(weeks=weeks)) for weeks in range(53)
]

activity_df = (
Expand Down
20 changes: 5 additions & 15 deletions buttercup/cogs/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,13 @@

from buttercup.cogs import ranks

username_regex = re.compile(
r"^(?P<prefix>(?P<leading_slash>/)?u/)?(?P<username>\S+)(?P<rest>.*)$"
)
username_regex = re.compile(r"^(?P<prefix>(?P<leading_slash>/)?u/)?(?P<username>\S+)(?P<rest>.*)$")
timezone_regex = re.compile(
r"UTC(?:(?P<hours>[+-]\d+(?:\.\d+)?)(?::(?P<minutes>\d+))?)?", re.RegexFlag.I
)

# First an amount and then a unit
relative_time_regex = re.compile(
r"^(?P<amount>\d+(?:\.\d+)?)\s*(?P<unit>\w*)\s*(?:ago\s*)?$"
)
relative_time_regex = re.compile(r"^(?P<amount>\d+(?:\.\d+)?)\s*(?P<unit>\w*)\s*(?:ago\s*)?$")
# The different time units
unit_regexes: Dict[str, re.Pattern] = {
"seconds": re.compile(r"^s(?:ec(?:ond)?s?)?$"),
Expand Down Expand Up @@ -120,9 +116,7 @@ def get_usernames_from_user_list(
:param author: The author of the message, taken as the default user.
:param limit: The maximum number of users to handle.
"""
raw_names = (
[user.strip() for user in user_list.split(" ")] if user_list is not None else []
)
raw_names = [user.strip() for user in user_list.split(" ")] if user_list is not None else []

if len(raw_names) == 0:
# No users provided, fall back to the author of the message
Expand Down Expand Up @@ -176,9 +170,7 @@ def get_initial_username_list(usernames: str, ctx: SlashContext) -> str:
return join_items_with_and(username_list)


def get_user(
username: str, ctx: SlashContext, blossom_api: BlossomAPI
) -> Optional[BlossomUser]:
def get_user(username: str, ctx: SlashContext, blossom_api: BlossomAPI) -> Optional[BlossomUser]:
"""Get the given user from Blossom.
Special keywords:
Expand Down Expand Up @@ -377,9 +369,7 @@ def get_progress_bar(
inner_space_count = width - inner_bar_count
outer_bar_count = bar_count - inner_bar_count

bar_str = (
f"[{'#' * inner_bar_count}{' ' * inner_space_count}]{'#' * outer_bar_count}"
)
bar_str = f"[{'#' * inner_bar_count}{' ' * inner_space_count}]{'#' * outer_bar_count}"
if as_code:
bar_str = f"`{bar_str}`"
count_str = f" ({count:,d}/{total:,d})" if display_count else ""
Expand Down
Loading

0 comments on commit a589c98

Please sign in to comment.