From b263e20ca27dc40c729dcf1314ac9f19a7d83335 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 14 Aug 2024 22:02:37 +0100 Subject: [PATCH 1/5] Remove all no-longer needed requirements --- requirements.txt | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9cc1b39..7a057bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 discord.py==2.4.0 dpytest==0.7.0 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-asyncio==0.23.8 pytest-cov==5.0.0 -schema==0.7.7 -smmap==5.0.1 -yarl==1.9.4 +schema==0.7.7 \ No newline at end of file From 92bc50396bc2f8e0ca7f2af8268ac9dd0b130e42 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 14 Aug 2024 22:42:53 +0100 Subject: [PATCH 2/5] Huge re-org to match normal python project structure --- .vscode/launch.json | 2 +- Dockerfile | 2 +- README.md | 6 +++--- py/matchy.py => matchy.py | 10 +++++----- matchy/__init__.py | 0 matchy/cogs/__init__.py | 0 py/cogs/matchy_cog.py => matchy/cogs/matchy.py | 10 +++++----- py/cogs/owner_cog.py => matchy/cogs/owner.py | 4 ++-- matchy/files/__init__.py | 0 {py => matchy/files}/config.py | 4 ++-- py/files.py => matchy/files/ops.py | 0 {py => matchy/files}/state.py | 9 +++++---- {py => matchy}/matching.py | 14 +++----------- {py => matchy}/util.py | 5 ----- matchy/views/__init__.py | 0 py/cogs/match_button.py => matchy/views/match.py | 2 +- tests/__init__.py | 0 {py => tests}/matching_test.py | 4 ++-- {py => tests}/owner_cog_test.py | 6 +++--- {py => tests}/state_test.py | 2 +- {scripts => tests}/test-cov.py | 0 {scripts => tests}/test.py | 2 +- 22 files changed, 35 insertions(+), 47 deletions(-) rename py/matchy.py => matchy.py (79%) mode change 100755 => 100644 create mode 100644 matchy/__init__.py create mode 100644 matchy/cogs/__init__.py rename py/cogs/matchy_cog.py => matchy/cogs/matchy.py (97%) rename py/cogs/owner_cog.py => matchy/cogs/owner.py (95%) create mode 100644 matchy/files/__init__.py rename {py => matchy/files}/config.py (98%) rename py/files.py => matchy/files/ops.py (100%) rename {py => matchy/files}/state.py (98%) rename {py => matchy}/matching.py (95%) rename {py => matchy}/util.py (91%) create mode 100644 matchy/views/__init__.py rename py/cogs/match_button.py => matchy/views/match.py (98%) create mode 100644 tests/__init__.py rename {py => tests}/matching_test.py (99%) rename {py => tests}/owner_cog_test.py (88%) rename {py => tests}/state_test.py (98%) rename {scripts => tests}/test-cov.py (100%) mode change 100755 => 100644 rename {scripts => tests}/test.py (81%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 26fafc0..3669159 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "Python Debugger: Matchy", "type": "debugpy", "request": "launch", - "program": "py/matchy.py", + "program": "matchy.py", "console": "integratedTerminal" } ] diff --git a/Dockerfile b/Dockerfile index b3c9015..1484132 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,4 +17,4 @@ RUN --mount=type=cache,target=/var/cache/buildkit/pip \ pip install --find-links /wheels --no-index -r requirements.txt COPY . . -CMD ["python", "py/matchy.py"] \ No newline at end of file +CMD ["python", "matchy.py"] \ No newline at end of file diff --git a/README.md b/README.md index ba2692f..cb5e8ef 100644 --- a/README.md +++ b/README.md @@ -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. ### 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 @@ -63,9 +63,9 @@ Matchy is configured by an optional `$MATCHY_CONFIG` envar or a `.matchy/config. ``` 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 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). diff --git a/py/matchy.py b/matchy.py old mode 100755 new mode 100644 similarity index 79% rename from py/matchy.py rename to matchy.py index 40f0316..ce3410e --- a/py/matchy.py +++ b/matchy.py @@ -5,9 +5,9 @@ import logging import discord from discord.ext import commands import os -from state import load_from_file -from cogs.matchy_cog import MatchyCog -from cogs.owner_cog import OwnerCog +from matchy.files.state import load_from_file +import matchy.cogs.matchy +import matchy.cogs.owner _STATE_FILE = ".matchy/state.json" state = load_from_file(_STATE_FILE) @@ -24,8 +24,8 @@ bot = commands.Bot(command_prefix='$', @bot.event async def setup_hook(): - await bot.add_cog(MatchyCog(bot, state)) - await bot.add_cog(OwnerCog(bot, state)) + await bot.add_cog(matchy.cogs.matchy.Cog(bot, state)) + await bot.add_cog(matchy.cogs.owner.Cog(bot, state)) @bot.event diff --git a/matchy/__init__.py b/matchy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/matchy/cogs/__init__.py b/matchy/cogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/cogs/matchy_cog.py b/matchy/cogs/matchy.py similarity index 97% rename from py/cogs/matchy_cog.py rename to matchy/cogs/matchy.py index 95cc1c7..c247e28 100644 --- a/py/cogs/matchy_cog.py +++ b/matchy/cogs/matchy.py @@ -7,16 +7,16 @@ from discord import app_commands from discord.ext import commands, tasks from datetime import datetime, timedelta, time -import cogs.match_button as match_button +import matchy.views.match as match import matching -from state import State, AuthScope +from matchy.files.state import State, AuthScope import util logger = logging.getLogger("cog") logger.setLevel(logging.INFO) -class MatchyCog(commands.Cog): +class Cog(commands.Cog): def __init__(self, bot: commands.Bot, state: State): self.bot = bot self.state = state @@ -25,7 +25,7 @@ class MatchyCog(commands.Cog): async def on_ready(self): """Bot is ready and connected""" self.run_hourly_tasks.start() - self.bot.add_dynamic_items(match_button.DynamicGroupButton) + self.bot.add_dynamic_items(match.DynamicGroupButton) activity = discord.Game("/join") await self.bot.change_presence(status=discord.Status.online, activity=activity) logger.info("Bot is up and ready!") @@ -180,7 +180,7 @@ class MatchyCog(commands.Cog): # Otherwise set up the button msg += "\n\nClick the button to match up groups and send them to the channel.\n" view = discord.ui.View(timeout=None) - view.add_item(match_button.DynamicGroupButton(members_min)) + view.add_item(match.DynamicGroupButton(members_min)) else: # Let a non-matcher know why they don't have the button msg += f"\n\nYou'll need the {AuthScope.MATCHER}" diff --git a/py/cogs/owner_cog.py b/matchy/cogs/owner.py similarity index 95% rename from py/cogs/owner_cog.py rename to matchy/cogs/owner.py index 8590d7e..84b2275 100644 --- a/py/cogs/owner_cog.py +++ b/matchy/cogs/owner.py @@ -3,13 +3,13 @@ Owner bot cog """ import logging from discord.ext import commands -from state import State, AuthScope +from matchy.files.state import State, AuthScope logger = logging.getLogger("owner") logger.setLevel(logging.INFO) -class OwnerCog(commands.Cog): +class Cog(commands.Cog): def __init__(self, bot: commands.Bot, state: State): self._bot = bot self._state = state diff --git a/matchy/files/__init__.py b/matchy/files/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/config.py b/matchy/files/config.py similarity index 98% rename from py/config.py rename to matchy/files/config.py index 07ff4ac..c157bc1 100644 --- a/py/config.py +++ b/matchy/files/config.py @@ -1,6 +1,6 @@ """Very simple config loading library""" from schema import Schema, Use, Optional -import files +import matchy.files.ops as ops import os import logging import json @@ -137,7 +137,7 @@ def _load() -> _Config: else: # Otherwise try the file if os.path.isfile(_FILE): - loaded = files.load(_FILE) + loaded = ops.load(_FILE) logger.info("Config loaded from %s", _FILE) else: loaded = _EMPTY_DICT diff --git a/py/files.py b/matchy/files/ops.py similarity index 100% rename from py/files.py rename to matchy/files/ops.py diff --git a/py/state.py b/matchy/files/state.py similarity index 98% rename from py/state.py rename to matchy/files/state.py index bbf9fab..3e36c1f 100644 --- a/py/state.py +++ b/matchy/files/state.py @@ -4,7 +4,7 @@ from datetime import datetime from schema import Schema, Use, Optional from collections.abc import Generator from typing import Protocol -import files +import matchy.files.ops as ops import copy import logging from contextlib import contextmanager @@ -351,6 +351,7 @@ class State(): # Set the value channel[key] = value + # TODO: Make this a decorator? @contextmanager def _safe_wrap_write(self): """Safely run any function wrapped in a validate""" @@ -369,7 +370,7 @@ class State(): def _save_to_file(self): """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): @@ -390,12 +391,12 @@ def load_from_file(file: str) -> State: # If there's a file load it and try to migrate if os.path.isfile(file): - loaded = files.load(file) + loaded = ops.load(file) _migrate(loaded) st = State(loaded, file) # Save out the migrated (or new) file - files.save(file, st._dict) + ops.save(file, st._dict) return st diff --git a/py/matching.py b/matchy/matching.py similarity index 95% rename from py/matching.py rename to matchy/matching.py index 1f3b485..c20422e 100644 --- a/py/matching.py +++ b/matchy/matching.py @@ -1,11 +1,11 @@ """Utility functions for matchy""" import logging import discord -from datetime import datetime, timedelta +from datetime import datetime 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 config +import matchy.files.config as config class _ScoreFactors(int): @@ -149,14 +149,6 @@ def attempt_create_groups(matchees: list[Member], 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): """Yields each shifted variation of the input list""" yield list diff --git a/py/util.py b/matchy/util.py similarity index 91% rename from py/util.py rename to matchy/util.py index 9b28f6d..ba15dd3 100644 --- a/py/util.py +++ b/matchy/util.py @@ -9,11 +9,6 @@ def get_day_with_suffix(day): 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: """Format the a given datetime""" num = get_day_with_suffix(time.day) diff --git a/matchy/views/__init__.py b/matchy/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/cogs/match_button.py b/matchy/views/match.py similarity index 98% rename from py/cogs/match_button.py rename to matchy/views/match.py index ba46518..932d3be 100644 --- a/py/cogs/match_button.py +++ b/matchy/views/match.py @@ -5,7 +5,7 @@ import logging import discord import re -import state +import matchy.files.state as state import matching logger = logging.getLogger("match_button") diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/matching_test.py b/tests/matching_test.py similarity index 99% rename from py/matching_test.py rename to tests/matching_test.py index 591cac2..b99c33f 100644 --- a/py/matching_test.py +++ b/tests/matching_test.py @@ -4,8 +4,8 @@ import discord import pytest import random -import matching -import state +import matchy.matching as matching +import matchy.files.state as state import copy import itertools from datetime import datetime, timedelta diff --git a/py/owner_cog_test.py b/tests/owner_cog_test.py similarity index 88% rename from py/owner_cog_test.py rename to tests/owner_cog_test.py index 0ea4387..2c22ea9 100644 --- a/py/owner_cog_test.py +++ b/tests/owner_cog_test.py @@ -2,10 +2,10 @@ import discord import discord.ext.commands as commands import pytest import pytest_asyncio -import state +import matchy.files.state as state 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 # TODO: Test more somehow, though it seems like dpytest is pretty incomplete @@ -20,7 +20,7 @@ async def bot(): b = commands.Bot(command_prefix="$", intents=intents) 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) yield b await dpytest.empty_queue() diff --git a/py/state_test.py b/tests/state_test.py similarity index 98% rename from py/state_test.py rename to tests/state_test.py index 51a7f4b..de7a185 100644 --- a/py/state_test.py +++ b/tests/state_test.py @@ -1,7 +1,7 @@ """ Test functions for the state module """ -import state +import matchy.files.state as state import tempfile import os diff --git a/scripts/test-cov.py b/tests/test-cov.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/test-cov.py rename to tests/test-cov.py diff --git a/scripts/test.py b/tests/test.py similarity index 81% rename from scripts/test.py rename to tests/test.py index ceebefe..ecd86fa 100644 --- a/scripts/test.py +++ b/tests/test.py @@ -4,7 +4,7 @@ from flake8.main.application import Application # Run flake 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() print(flake_exitcode) From abbae361c79cc9e2057bd38e0bfdd114fcb6aa69 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 14 Aug 2024 22:45:35 +0100 Subject: [PATCH 3/5] Fix the test path --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 90a8904..20a9d56 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,4 +20,4 @@ jobs: python -m pip install -r requirements.txt - name: Run tests run: | - python scripts/test.py \ No newline at end of file + python tests/test.py \ No newline at end of file From be7a1d080a59aae4ccb170e36c01594b62ebebf8 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 14 Aug 2024 22:49:36 +0100 Subject: [PATCH 4/5] Combine the two workflows so publish can depend on test --- .../{publish.yml => test-and-publish.yml} | 26 +++++++++++++++++-- .github/workflows/test.yml | 23 ---------------- 2 files changed, 24 insertions(+), 25 deletions(-) rename .github/workflows/{publish.yml => test-and-publish.yml} (88%) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/test-and-publish.yml similarity index 88% rename from .github/workflows/publish.yml rename to .github/workflows/test-and-publish.yml index d785224..e89df20 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/test-and-publish.yml @@ -1,4 +1,4 @@ -name: Build and Push Docker +name: Run Tests on: push: @@ -14,10 +14,32 @@ env: REGISTRY: ghcr.io 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: + + # 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: runs-on: ubuntu-latest + needs: test # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 20a9d56..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -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 tests/test.py \ No newline at end of file From edeeaf578ad15937a7472b7659797b5952a29121 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 14 Aug 2024 22:50:36 +0100 Subject: [PATCH 5/5] Fix the import in matching.py --- matchy/matching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matchy/matching.py b/matchy/matching.py index c20422e..5d9b9b2 100644 --- a/matchy/matching.py +++ b/matchy/matching.py @@ -4,7 +4,7 @@ import discord from datetime import datetime from typing import Protocol, runtime_checkable from matchy.files.state import State, ts_to_datetime -import util +import matchy.util as util import matchy.files.config as config