Merge pull request #10 from mdiluz/reformat-cleaup
Big reformat and cleanup
This commit is contained in:
commit
04e9e3a5b1
25 changed files with 61 additions and 93 deletions
|
@ -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:
|
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
|
@ -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
2
.vscode/launch.json
vendored
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"]
|
|
@ -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
10
py/matchy.py → matchy.py
Executable file → Normal 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
0
matchy/__init__.py
Normal file
0
matchy/cogs/__init__.py
Normal file
0
matchy/cogs/__init__.py
Normal 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}"
|
|
@ -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
0
matchy/files/__init__.py
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
0
matchy/views/__init__.py
Normal 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")
|
|
@ -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
0
tests/__init__.py
Normal 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
|
|
@ -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()
|
|
@ -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
0
scripts/test-cov.py → tests/test-cov.py
Executable file → Normal 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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue