Merge pull request #10 from mdiluz/reformat-cleaup

Big reformat and cleanup
This commit is contained in:
Marc Di Luzio 2024-08-14 22:56:48 +01:00 committed by GitHub
commit 04e9e3a5b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 61 additions and 93 deletions

View file

@ -1,4 +1,4 @@
name: Build and Push Docker name: Run Tests
on: on:
push: push:
@ -14,10 +14,32 @@ env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs: jobs:
# Core test runner
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Run tests
run: |
python tests/test.py
build-and-push-images: build-and-push-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions: permissions:

View file

@ -1,23 +0,0 @@
name: Run Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Run tests
run: |
python scripts/test.py

2
.vscode/launch.json vendored
View file

@ -8,7 +8,7 @@
"name": "Python Debugger: Matchy", "name": "Python Debugger: Matchy",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "py/matchy.py", "program": "matchy.py",
"console": "integratedTerminal" "console": "integratedTerminal"
} }
] ]

View file

@ -17,4 +17,4 @@ RUN --mount=type=cache,target=/var/cache/buildkit/pip \
pip install --find-links /wheels --no-index -r requirements.txt pip install --find-links /wheels --no-index -r requirements.txt
COPY . . COPY . .
CMD ["python", "py/matchy.py"] CMD ["python", "matchy.py"]

View file

@ -42,7 +42,7 @@ git checkout -b [feature-branch-name]
VSCode can be configured to use this new `.venv` and is the recommended way to develop. VSCode can be configured to use this new `.venv` and is the recommended way to develop.
### Tests ### Tests
Python tests are written to use `pytest` and cover most internal functionality. Tests can be run in the same way as in the Github Actions with [`scripts/test.py`](`scripts/test.py`), which lints all python code and runs any tests with `pytest`. A helper script [`scripts/test-cov.py`](scripts/test-cov.py) is available to generate a html view on current code coverage. Python tests are written to use `pytest` and cover most internal functionality. Tests can be run in the same way as in the Github Actions with [`test.py`](`tests/test.py`), which lints all python code and runs any tests with `pytest`. A helper script [`test-cov.py`](tests/test-cov.py) is available to generate a html view on current code coverage.
## Hosting ## Hosting
@ -63,9 +63,9 @@ Matchy is configured by an optional `$MATCHY_CONFIG` envar or a `.matchy/config.
``` ```
Only the version is required. Only the version is required.
See [`py/config.py`](py/config.py) for explanations for any extra settings here. See [`config.py`](matchy/files/config.py) for explanations for any extra settings here.
_State_ is stored locally in a `.matchy/state.json` file. This will be created by the bot. This stores historical information on users, maching schedules, user auth scopes and more. See [`py/state.py`](py/state.py) for schema information if you need to inspect it. _State_ is stored locally in a `.matchy/state.json` file. This will be created by the bot. This stores historical information on users, maching schedules, user auth scopes and more. See [`state.py`](matchy/files/state.py) for schema information if you need to inspect it.
### Secrets ### Secrets
The `TOKEN` envar is required run the bot. It's recommended this is placed in a local `.env` file. To generate bot token for development see [this discord.py guide](https://discordpy.readthedocs.io/en/stable/discord.html). The `TOKEN` envar is required run the bot. It's recommended this is placed in a local `.env` file. To generate bot token for development see [this discord.py guide](https://discordpy.readthedocs.io/en/stable/discord.html).

10
py/matchy.py → matchy.py Executable file → Normal file
View file

@ -5,9 +5,9 @@ import logging
import discord import discord
from discord.ext import commands from discord.ext import commands
import os import os
from state import load_from_file from matchy.files.state import load_from_file
from cogs.matchy_cog import MatchyCog import matchy.cogs.matchy
from cogs.owner_cog import OwnerCog import matchy.cogs.owner
_STATE_FILE = ".matchy/state.json" _STATE_FILE = ".matchy/state.json"
state = load_from_file(_STATE_FILE) state = load_from_file(_STATE_FILE)
@ -24,8 +24,8 @@ bot = commands.Bot(command_prefix='$',
@bot.event @bot.event
async def setup_hook(): async def setup_hook():
await bot.add_cog(MatchyCog(bot, state)) await bot.add_cog(matchy.cogs.matchy.Cog(bot, state))
await bot.add_cog(OwnerCog(bot, state)) await bot.add_cog(matchy.cogs.owner.Cog(bot, state))
@bot.event @bot.event

0
matchy/__init__.py Normal file
View file

0
matchy/cogs/__init__.py Normal file
View file

View file

@ -7,16 +7,16 @@ from discord import app_commands
from discord.ext import commands, tasks from discord.ext import commands, tasks
from datetime import datetime, timedelta, time from datetime import datetime, timedelta, time
import cogs.match_button as match_button import matchy.views.match as match
import matching import matching
from state import State, AuthScope from matchy.files.state import State, AuthScope
import util import util
logger = logging.getLogger("cog") logger = logging.getLogger("cog")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
class MatchyCog(commands.Cog): class Cog(commands.Cog):
def __init__(self, bot: commands.Bot, state: State): def __init__(self, bot: commands.Bot, state: State):
self.bot = bot self.bot = bot
self.state = state self.state = state
@ -25,7 +25,7 @@ class MatchyCog(commands.Cog):
async def on_ready(self): async def on_ready(self):
"""Bot is ready and connected""" """Bot is ready and connected"""
self.run_hourly_tasks.start() self.run_hourly_tasks.start()
self.bot.add_dynamic_items(match_button.DynamicGroupButton) self.bot.add_dynamic_items(match.DynamicGroupButton)
activity = discord.Game("/join") activity = discord.Game("/join")
await self.bot.change_presence(status=discord.Status.online, activity=activity) await self.bot.change_presence(status=discord.Status.online, activity=activity)
logger.info("Bot is up and ready!") logger.info("Bot is up and ready!")
@ -180,7 +180,7 @@ class MatchyCog(commands.Cog):
# Otherwise set up the button # Otherwise set up the button
msg += "\n\nClick the button to match up groups and send them to the channel.\n" msg += "\n\nClick the button to match up groups and send them to the channel.\n"
view = discord.ui.View(timeout=None) view = discord.ui.View(timeout=None)
view.add_item(match_button.DynamicGroupButton(members_min)) view.add_item(match.DynamicGroupButton(members_min))
else: else:
# Let a non-matcher know why they don't have the button # Let a non-matcher know why they don't have the button
msg += f"\n\nYou'll need the {AuthScope.MATCHER}" msg += f"\n\nYou'll need the {AuthScope.MATCHER}"

View file

@ -3,13 +3,13 @@ Owner bot cog
""" """
import logging import logging
from discord.ext import commands from discord.ext import commands
from state import State, AuthScope from matchy.files.state import State, AuthScope
logger = logging.getLogger("owner") logger = logging.getLogger("owner")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
class OwnerCog(commands.Cog): class Cog(commands.Cog):
def __init__(self, bot: commands.Bot, state: State): def __init__(self, bot: commands.Bot, state: State):
self._bot = bot self._bot = bot
self._state = state self._state = state

0
matchy/files/__init__.py Normal file
View file

View file

@ -1,6 +1,6 @@
"""Very simple config loading library""" """Very simple config loading library"""
from schema import Schema, Use, Optional from schema import Schema, Use, Optional
import files import matchy.files.ops as ops
import os import os
import logging import logging
import json import json
@ -137,7 +137,7 @@ def _load() -> _Config:
else: else:
# Otherwise try the file # Otherwise try the file
if os.path.isfile(_FILE): if os.path.isfile(_FILE):
loaded = files.load(_FILE) loaded = ops.load(_FILE)
logger.info("Config loaded from %s", _FILE) logger.info("Config loaded from %s", _FILE)
else: else:
loaded = _EMPTY_DICT loaded = _EMPTY_DICT

View file

@ -4,7 +4,7 @@ from datetime import datetime
from schema import Schema, Use, Optional from schema import Schema, Use, Optional
from collections.abc import Generator from collections.abc import Generator
from typing import Protocol from typing import Protocol
import files import matchy.files.ops as ops
import copy import copy
import logging import logging
from contextlib import contextmanager from contextlib import contextmanager
@ -351,6 +351,7 @@ class State():
# Set the value # Set the value
channel[key] = value channel[key] = value
# TODO: Make this a decorator?
@contextmanager @contextmanager
def _safe_wrap_write(self): def _safe_wrap_write(self):
"""Safely run any function wrapped in a validate""" """Safely run any function wrapped in a validate"""
@ -369,7 +370,7 @@ class State():
def _save_to_file(self): def _save_to_file(self):
"""Saves the state out to the chosen file""" """Saves the state out to the chosen file"""
files.save(self._file, self.dict_internal_copy) ops.save(self._file, self.dict_internal_copy)
def _migrate(dict: dict): def _migrate(dict: dict):
@ -390,12 +391,12 @@ def load_from_file(file: str) -> State:
# If there's a file load it and try to migrate # If there's a file load it and try to migrate
if os.path.isfile(file): if os.path.isfile(file):
loaded = files.load(file) loaded = ops.load(file)
_migrate(loaded) _migrate(loaded)
st = State(loaded, file) st = State(loaded, file)
# Save out the migrated (or new) file # Save out the migrated (or new) file
files.save(file, st._dict) ops.save(file, st._dict)
return st return st

View file

@ -1,11 +1,11 @@
"""Utility functions for matchy""" """Utility functions for matchy"""
import logging import logging
import discord import discord
from datetime import datetime, timedelta from datetime import datetime
from typing import Protocol, runtime_checkable from typing import Protocol, runtime_checkable
from state import State, ts_to_datetime from matchy.files.state import State, ts_to_datetime
import util import matchy.util as util
import config import matchy.files.config as config
class _ScoreFactors(int): class _ScoreFactors(int):
@ -149,14 +149,6 @@ def attempt_create_groups(matchees: list[Member],
return groups return groups
def datetime_range(start_time: datetime, increment: timedelta, end: datetime):
"""Yields a datetime range with a given increment"""
current = start_time
while current <= end or end is None:
yield current
current += increment
def iterate_all_shifts(list: list): def iterate_all_shifts(list: list):
"""Yields each shifted variation of the input list""" """Yields each shifted variation of the input list"""
yield list yield list

View file

@ -9,11 +9,6 @@ def get_day_with_suffix(day):
return str(day) + {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th') return str(day) + {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th')
def format_today() -> str:
"""Format the current datetime"""
return format_day(datetime.now())
def format_day(time: datetime) -> str: def format_day(time: datetime) -> str:
"""Format the a given datetime""" """Format the a given datetime"""
num = get_day_with_suffix(time.day) num = get_day_with_suffix(time.day)

0
matchy/views/__init__.py Normal file
View file

View file

@ -5,7 +5,7 @@ import logging
import discord import discord
import re import re
import state import matchy.files.state as state
import matching import matching
logger = logging.getLogger("match_button") logger = logging.getLogger("match_button")

View file

@ -1,27 +1,8 @@
aiohappyeyeballs==2.3.5
aiohttp==3.10.3
aiosignal==1.3.1
attrs==24.2.0
autopep8==2.3.1
coverage==7.6.1 coverage==7.6.1
discord.py==2.4.0 discord.py==2.4.0
dpytest==0.7.0 dpytest==0.7.0
flake8==7.1.1 flake8==7.1.1
frozenlist==1.4.1
gitdb==4.0.11
GitPython==3.1.43
idna==3.7
iniconfig==2.0.0
mccabe==0.7.0
multidict==6.0.5
overrides==7.7.0
packaging==24.1
pluggy==1.5.0
pycodestyle==2.12.1
pyflakes==3.2.0
pytest==8.3.2 pytest==8.3.2
pytest-asyncio==0.23.8 pytest-asyncio==0.23.8
pytest-cov==5.0.0 pytest-cov==5.0.0
schema==0.7.7 schema==0.7.7
smmap==5.0.1
yarl==1.9.4

0
tests/__init__.py Normal file
View file

View file

@ -4,8 +4,8 @@
import discord import discord
import pytest import pytest
import random import random
import matching import matchy.matching as matching
import state import matchy.files.state as state
import copy import copy
import itertools import itertools
from datetime import datetime, timedelta from datetime import datetime, timedelta

View file

@ -2,10 +2,10 @@ import discord
import discord.ext.commands as commands import discord.ext.commands as commands
import pytest import pytest
import pytest_asyncio import pytest_asyncio
import state import matchy.files.state as state
import discord.ext.test as dpytest import discord.ext.test as dpytest
from cogs.owner_cog import OwnerCog from matchy.cogs.owner import Cog
# Primarily borrowing from https://dpytest.readthedocs.io/en/latest/tutorials/using_pytest.html # Primarily borrowing from https://dpytest.readthedocs.io/en/latest/tutorials/using_pytest.html
# TODO: Test more somehow, though it seems like dpytest is pretty incomplete # TODO: Test more somehow, though it seems like dpytest is pretty incomplete
@ -20,7 +20,7 @@ async def bot():
b = commands.Bot(command_prefix="$", b = commands.Bot(command_prefix="$",
intents=intents) intents=intents)
await b._async_setup_hook() await b._async_setup_hook()
await b.add_cog(OwnerCog(b, state.State(state._EMPTY_DICT))) await b.add_cog(Cog(b, state.State(state._EMPTY_DICT)))
dpytest.configure(b) dpytest.configure(b)
yield b yield b
await dpytest.empty_queue() await dpytest.empty_queue()

View file

@ -1,7 +1,7 @@
""" """
Test functions for the state module Test functions for the state module
""" """
import state import matchy.files.state as state
import tempfile import tempfile
import os import os

0
scripts/test-cov.py → tests/test-cov.py Executable file → Normal file
View file

View file

@ -4,7 +4,7 @@ from flake8.main.application import Application
# Run flake # Run flake
app = Application() app = Application()
ret = app.run(["--max-line-length", "120", "py/", "scripts/"]) ret = app.run(["--max-line-length", "120", "matchy/", "tests/"])
flake_exitcode = app.exit_code() flake_exitcode = app.exit_code()
print(flake_exitcode) print(flake_exitcode)