Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Switch to uv and ruff #86

Merged
merged 5 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@ on:
workflow_call:

jobs:
black:
name: Black
lint_and_format:
name: Lint and Format
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install
uv sync

- name: Check code formatting with Black
run: poetry run black . --check
- name: Check for code errors
run: |
uv run ruff check

- name: Check formatting
run: |
uv run ruff format --check
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.python-version
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version
rev: v0.7.3
hooks:
# Run the linter
- id: ruff
args: [ --fix ]
# Run the formatter
- id: ruff-format
32 changes: 22 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
# Base image
FROM python:3.11-slim AS base
# Use a Python image with uv pre-installed
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

# Install the project into `/app`
WORKDIR /app

# Install dependencies
COPY pyproject.toml poetry.lock ./
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1

RUN pip install poetry \
&& poetry config virtualenvs.create false \
&& poetry install --no-dev --no-interaction --no-ansi
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy

# Copy the rest of the application code
COPY . .
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-dev

# Then, add the rest of the project source code and install it
# Installing separately from its dependencies allows optimal layer caching
ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev

# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"

# Run the bot
CMD ["poetry", "run", "python", "src/main.py"]
ENTRYPOINT ["uv", "run", "python", "src/main.py"]
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,30 @@ DuckBot is a Discord bot written in Python using the discord.py library for the

To get started, please follow these steps:

1. Install Poetry and add it to your PATH if not already installed:
1. Install `uv` if not already installed:

Linux, macOS, Windows (WSL)
```bash
curl -sSL https://install.python-poetry.org | python3 -
export PATH="$HOME/.local/bin:$PATH"
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Windows (Powershell)
```powershell
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
setx PATH "%APPDATA%\Python\Scripts;%PATH%"
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

2. Install the dependencies.
2. Install dependencies:

```bash
poetry install
```sh
uv sync
uv run pre-commit install
```

3. Copy `.env.example` to a new file `.env` and set required environment variables.

4. Run the bot.

```bash
poetry run python src/main.py
uv run python src/main.py
```

## Contributing
Expand Down
45 changes: 26 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
[tool.poetry]
[project]
name = "duckbot"
version = "0.1.0"
version = "1.0.0"
description = "DuckBot is the CS Club's Discord Bot, created by the CS Club Open Source Team."
authors = ["CS Club Open Source Team <[email protected]>"]
authors = [
{ name = "CS Club Open Source Team", email = "[email protected]" }
]
license = "MIT"
readme = "README.md"
package-mode = false
requires-python = ">=3.12"
dependencies = [
"aiosqlite>=0.20.0",
"discord-py>=2.4.0",
"google-generativeai>=0.8.3",
"levenshtein>=0.26.1",
"matplotlib>=3.9.2",
"pathlib>=1.0.1",
"python-dotenv>=1.0.1",
"pytz>=2024.2",
"schedule>=1.2.2",
]

[tool.poetry.dependencies]
python = "^3.11"
"discord.py" = "2.3.2"
black = "^24.4.2"
poetry-dotenv-plugin = "^0.2.0"
google-generativeai = "^0.7.2"
pathlib = "^1.0.1"
aiosqlite = "^0.20.0"
schedule = "^1.2.2"
pytz = "^2024.1"
matplotlib = "^3.9.2"
levenshtein = "^0.26.1"
[tool.ruff]
lint.select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I']
lint.ignore = ["E501"]
lint.fixable = ["ALL"]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[dependency-groups]
dev = [
"pre-commit>=4.0.1",
"ruff>=0.7.3",
]
9 changes: 7 additions & 2 deletions src/commands/admin_commands.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import os
import logging
from discord import app_commands, Interaction, Embed
import os

from discord import Embed, Interaction, app_commands
from dotenv import load_dotenv

from models.databases.admin_settings_db import AdminSettingsDB

load_dotenv()

# Retrieve the list of admin usernames from the .env file
ADMIN_USERS = os.getenv("ADMIN_USERS", "").split(",")

Expand Down
4 changes: 2 additions & 2 deletions src/commands/duck_related.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Optional

import aiohttp
from discord import app_commands, Interaction, Embed
from discord import Embed, Interaction, app_commands

from constants.duck_data import DUCK_FACTS, DUCK_JOKES
from utils.tenor import get_tenor_gif
Expand Down Expand Up @@ -39,7 +39,7 @@ async def duck_gif(self, interaction: Interaction):
gif_url = await get_tenor_gif(search_term)
if gif_url:
# Create an embed with the GIF
embed = Embed(title=f"Here's a random duck gif!")
embed = Embed(title="Here's a random duck gif!")
embed.set_image(url=gif_url)
await interaction.followup.send(embed=embed)
else:
Expand Down
5 changes: 3 additions & 2 deletions src/commands/faq.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import datetime as dt
from math import ceil, floor

import pytz
from math import floor, ceil
from discord import app_commands, Interaction, File
from discord import File, Interaction, app_commands


class FAQGroup(app_commands.Group):
Expand Down
10 changes: 5 additions & 5 deletions src/commands/gemini.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
from enum import IntEnum
import csv
import logging
import os
import os.path
import random
import re
import tempfile
import time
from collections import defaultdict
import random
import requests
from enum import IntEnum

from discord import Embed
from google.generativeai.types import HarmCategory, HarmBlockThreshold, File
import google.generativeai as genai
import requests
from discord import Embed
from dotenv import load_dotenv
from google.generativeai.types import File, HarmBlockThreshold, HarmCategory

from constants.colours import LIGHT_YELLOW

Expand Down
12 changes: 6 additions & 6 deletions src/commands/help_menu.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import os

from discord import (
Object,
Embed,
ui,
Color,
app_commands,
SelectOption,
ButtonStyle,
Color,
Embed,
Interaction,
Object,
SelectOption,
app_commands,
ui,
)

GUILD_ID = int(os.environ["GUILD_ID"])
Expand Down
2 changes: 1 addition & 1 deletion src/commands/hi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from discord import app_commands, Interaction
from discord import Interaction, app_commands


class HiGroup(app_commands.Group):
Expand Down
10 changes: 5 additions & 5 deletions src/commands/skullboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from collections import Counter
from functools import wraps
from io import BytesIO
from typing import Callable, Awaitable
from typing import Awaitable, Callable

import requests
from discord import (
AllowedMentions,
Client,
Expand All @@ -17,12 +18,11 @@
)
from discord.errors import NotFound
from discord.utils import MISSING
import requests

from constants.colours import LIGHT_GREY
from models.databases.skullboard_database import SkullboardDB
from utils import time
from utils.plotting import get_histogram_image
from models.databases.skullboard_database import SkullboardDB


class SkullboardManager:
Expand Down Expand Up @@ -458,12 +458,12 @@ async def user(self, interaction: Interaction, member: Member) -> Response:
user_name = member.name

data = await self.db.get_user_rankings(999999) # get all
data = [(id, freq) for id, freq in data if freq > 0]
data = [(u_id, freq) for u_id, freq in data if freq > 0]

if not data:
raise Exception("Database Error")

user = [(id, freq) for id, freq in data if id == user_id]
user = [(u_id, freq) for u_id, freq in data if u_id == user_id]
if not user:
user = [(user_id, 0)]
_, user_skull_count = user[0]
Expand Down
26 changes: 12 additions & 14 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import os
import importlib
import pkgutil
import asyncio
import importlib
import logging
import os
import pkgutil

from discord import (
Attachment,
Embed,
Intents,
app_commands,
Object,
Interaction,
Embed,
Message,
Object,
RawReactionActionEvent,
Attachment,
app_commands,
)

from discord.errors import NotFound
from discord.ext import commands
from dotenv import load_dotenv

from commands import admin_commands, gemini, help_menu, skullboard
from constants.colours import LIGHT_YELLOW
from commands import gemini, skullboard, help_menu, admin_commands
from utils import time, spam_detection
from utils import spam_detection, time

# Load environment variables from .env file
load_dotenv()
Expand Down Expand Up @@ -61,7 +60,7 @@ def __init__(self):
datefmt="%Y-%m-%d %H:%M:%S",
level=logging.INFO, # Minimum log level to capture
)
logging.info(f"Started Bot")
logging.info("Started Bot")

# Initialise gemini model
self.gemini_model = gemini.GeminiBot(
Expand Down Expand Up @@ -136,10 +135,9 @@ async def ping(interaction: Interaction):

@client.tree.command(description="Ask DuckBot anything!", guild=Object(GUILD_ID))
async def chat(interaction: Interaction, query: str | None, file: Attachment | None):

try:
await interaction.response.defer()
query = "" if query == None else query
query = "" if query is None else query
bot_response = await client.gemini_model.query(
message=query,
attachment=file,
Expand Down Expand Up @@ -176,7 +174,7 @@ async def chat(interaction: Interaction, query: str | None, file: Attachment | N
description="View useful information about using the bot.",
guild=Object(GUILD_ID),
)
async def help(interaction: Interaction):
async def help(interaction: Interaction): # noqa: A001
Help_Menu_View = help_menu.HelpMenu(client) # Creating the view for buttons
Help_Menu_View.update_select_options() # Creating options for select menu
embed = Help_Menu_View.create_help_embed(0)
Expand Down
Loading