Skip to content

Commit

Permalink
chore: Switch to uv and ruff (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenixpereira authored Nov 29, 2024
1 parent 2279c0a commit 0b3f3ef
Show file tree
Hide file tree
Showing 21 changed files with 1,381 additions and 93 deletions.
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 <dev@csclub.org.au>"]
authors = [
{ name = "CS Club Open Source Team", email = "dev@csclub.org.au" }
]
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

0 comments on commit 0b3f3ef

Please sign in to comment.