Skip to content

Commit

Permalink
Merge pull request #60 from python-discord/bump-various-versions
Browse files Browse the repository at this point in the history
Bump various versions
  • Loading branch information
ChrisLovering authored Aug 22, 2023
2 parents 1e589e6 + fe0ccf6 commit a526080
Show file tree
Hide file tree
Showing 16 changed files with 774 additions and 536 deletions.
15 changes: 8 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ jobs:
echo "::set-output name=tag::$tag"
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2

- name: Login to Github Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
Expand All @@ -38,7 +38,7 @@ jobs:
# Github Container Registry tagged with "latest" and
# the short SHA of the commit.
- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
Expand All @@ -48,16 +48,17 @@ jobs:
ghcr.io/python-discord/metricity:latest
ghcr.io/python-discord/metricity:${{ steps.sha_tag.outputs.tag }}
- uses: azure/setup-kubectl@v3

- name: Authenticate with Kubernetes
uses: azure/k8s-set-context@v1
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}

- name: Deploy to Kubernetes
uses: Azure/k8s-deploy@v1
uses: Azure/k8s-deploy@v4
with:
manifests: |
deployment.yaml
images: 'ghcr.io/python-discord/metricity:${{ steps.sha_tag.outputs.tag }}'
kubectl-version: 'latest'
10 changes: 4 additions & 6 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout branch
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Install Python Dependencies
uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1
uses: HassanAbouelela/actions/setup-python@setup-python_v1.4.1
with:
# Set dev=true to install flake8 extensions, which are dev dependencies
dev: true
python_version: '3.10'

- name: Lint code with Flake8
run: poetry run flake8 . --count --show-source --statistics
- name: Lint code with ruff
run: ruff check --format=github .
16 changes: 6 additions & 10 deletions create_metricity_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@


def parse_db_url(db_url: str) -> SplitResult:
"""Validate and split the given databse url."""
"""Validate and split the given database url."""
db_url_parts = urlsplit(db_url)
if not all((
db_url_parts.hostname,
db_url_parts.username,
db_url_parts.password
db_url_parts.password,
)):
raise ValueError(
"The given db_url is not a valid PostgreSQL database URL."
)
raise ValueError("The given db_url is not a valid PostgreSQL database URL.")
return db_url_parts


Expand All @@ -28,7 +26,7 @@ def parse_db_url(db_url: str) -> SplitResult:
host=database_parts.hostname,
port=database_parts.port,
user=database_parts.username,
password=database_parts.password
password=database_parts.password,
)

db_name = database_parts.path[1:] or "metricity"
Expand All @@ -39,8 +37,6 @@ def parse_db_url(db_url: str) -> SplitResult:
cursor.execute("SELECT 1 FROM pg_catalog.pg_database WHERE datname = %s", (db_name,))
exists = cursor.fetchone()
if not exists:
print("Creating metricity database.")
cursor.execute(
sql.SQL("CREATE DATABASE {dbname}").format(dbname=sql.Identifier(db_name))
)
print("Creating metricity database.") # noqa: T201
cursor.execute(sql.SQL("CREATE DATABASE {dbname}").format(dbname=sql.Identifier(db_name)))
conn.close()
1 change: 0 additions & 1 deletion metricity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import os
from typing import TYPE_CHECKING


import coloredlogs
from pydis_core.utils import apply_monkey_patches

Expand Down
2 changes: 1 addition & 1 deletion metricity/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async def main() -> None:
presences=False,
messages=True,
reactions=False,
typing=False
typing=False,
)

async with aiohttp.ClientSession() as session:
Expand Down
6 changes: 3 additions & 3 deletions metricity/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ def __init__(self, *args, **kwargs) -> None:
async def setup_hook(self) -> None:
"""Connect to db and load cogs."""
await super().setup_hook()
log.info(f"Metricity is online, logged in as {self.user}")
log.info("Metricity is online, logged in as %s", self.user)
await connect()
await self.load_extensions(exts)

async def on_error(self, event: str, *args, **kwargs) -> None:
async def on_error(self, event: str, *_args, **_kwargs) -> None:
"""Log errors raised in event listeners rather than printing them to stderr."""
log.exception(f"Unhandled exception in {event}.")
log.exception("Unhandled exception in %s", event)
60 changes: 27 additions & 33 deletions metricity/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from os import environ
from pathlib import Path
from typing import Any, Optional
from typing import Any

import toml
from deepmerge import Merger
Expand All @@ -28,33 +28,31 @@ def get_section(section: str) -> dict[str, Any]:
if not Path("config-default.toml").exists():
raise MetricityConfigurationError("config-default.toml is missing")

with open("config-default.toml", "r") as default_config_file:
with Path.open("config-default.toml") as default_config_file:
default_config = toml.load(default_config_file)

# Load user configuration
user_config = {}
user_config_location = Path(environ.get("CONFIG_LOCATION", "./config.toml"))

if user_config_location.exists():
with open(user_config_location, "r") as user_config_file:
with Path.open(user_config_location) as user_config_file:
user_config = toml.load(user_config_file)

# Merge the configuration
merger = Merger(
[
(dict, "merge")
(dict, "merge"),
],
["override"],
["override"]
["override"],
)

conf = merger.merge(default_config, user_config)

# Check whether we are missing the requested section
if not conf.get(section):
raise MetricityConfigurationError(
f"Config is missing section '{section}'"
)
raise MetricityConfigurationError(f"Config is missing section '{section}'")

return conf[section]

Expand All @@ -66,34 +64,30 @@ def __new__(
cls: type,
name: str,
bases: tuple[type],
dictionary: dict[str, Any]
dictionary: dict[str, Any],
) -> type:
"""Use the section attr in the subclass to fill in the values from the TOML."""
config = get_section(dictionary["section"])

log.info(f"Loading configuration section {dictionary['section']}")
log.info("Loading configuration section %s", dictionary["section"])

for key, value in config.items():
if isinstance(value, dict):
if env_var := value.get("env"):
if env_value := environ.get(env_var):
config[key] = env_value
else:
if not value.get("optional"):
raise MetricityConfigurationError(
f"Required config option '{key}' in"
f" '{dictionary['section']}' is missing, either set"
f" the environment variable {env_var} or override "
"it in your config.toml file"
)
else:
config[key] = None
if isinstance(value, dict) and (env_var := value.get("env")):
if env_value := environ.get(env_var):
config[key] = env_value
elif not value.get("optional"):
raise MetricityConfigurationError(
f"Required config option '{key}' in"
f" '{dictionary['section']}' is missing, either set"
f" the environment variable {env_var} or override "
"it in your config.toml file",
)
else:
config[key] = None

dictionary.update(config)

config_section = super().__new__(cls, name, bases, dictionary)

return config_section
return super().__new__(cls, name, bases, dictionary)


class PythonConfig(metaclass=ConfigSection):
Expand Down Expand Up @@ -125,10 +119,10 @@ class DatabaseConfig(metaclass=ConfigSection):

section = "database"

uri: Optional[str]
uri: str | None

host: Optional[str]
port: Optional[int]
database: Optional[str]
username: Optional[str]
password: Optional[str]
host: str | None
port: int | None
database: str | None
username: str | None
password: str | None
8 changes: 3 additions & 5 deletions metricity/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,15 @@ class TZDateTime(TypeDecorator):
impl = DateTime
cache_ok = True

def process_bind_param(self, value: datetime, dialect: Dialect) -> datetime:
def process_bind_param(self, value: datetime, _dialect: Dialect) -> datetime:
"""Convert the value to aware before saving to db."""
if value is not None:
if not value.tzinfo:
raise TypeError("tzinfo is required")
value = value.astimezone(timezone.utc).replace(
tzinfo=None
)
value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value

def process_result_value(self, value: datetime, dialect: Dialect) -> datetime:
def process_result_value(self, value: datetime, _dialect: Dialect) -> datetime:
"""Convert the value to aware before passing back to user-land."""
if value is not None:
value = value.replace(tzinfo=timezone.utc)
Expand Down
9 changes: 6 additions & 3 deletions metricity/exts/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ def _get_error_embed(self, title: str, body: str) -> discord.Embed:
return discord.Embed(
title=title,
colour=discord.Colour.red(),
description=body
description=body,
)

@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, e: commands.errors.CommandError) -> None:
"""Provide generic command error handling."""
if isinstance(e, SUPPRESSED_ERRORS):
log.debug(
f"Command {ctx.invoked_with} invoked by {ctx.message.author} with error "
f"{e.__class__.__name__}: {e}"
"Command %s invoked by %s with error %s: %s",
ctx.invoked_with,
ctx.message.author,
e.__class__.__name__,
e,
)


Expand Down
4 changes: 2 additions & 2 deletions metricity/exts/event_listeners/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async def insert_thread(thread: discord.Thread) -> None:
)


async def sync_message(message: discord.Message, from_thread: bool) -> None:
async def sync_message(message: discord.Message, *, from_thread: bool) -> None:
"""Sync the given message with the database."""
if await models.Message.get(str(message.id)):
return
Expand All @@ -25,7 +25,7 @@ async def sync_message(message: discord.Message, from_thread: bool) -> None:
"id": str(message.id),
"channel_id": str(message.channel.id),
"author_id": str(message.author.id),
"created_at": message.created_at
"created_at": message.created_at,
}

if from_thread:
Expand Down
26 changes: 13 additions & 13 deletions metricity/exts/event_listeners/guild_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ async def sync_guild(self) -> None:
log.info("Beginning user synchronisation process")
await models.User.update.values(in_guild=False).gino.status()

users = []
for user in guild.members:
users.append({
users = [
{
"id": str(user.id),
"name": user.name,
"avatar_hash": getattr(user.avatar, "key", None),
Expand All @@ -46,15 +45,17 @@ async def sync_guild(self) -> None:
"bot": user.bot,
"in_guild": True,
"public_flags": dict(user.public_flags),
"pending": user.pending
})
"pending": user.pending,
}
for user in guild.members
]

log.info(f"Performing bulk upsert of {len(users)} rows")
log.info("Performing bulk upsert of %d rows", len(users))

user_chunks = discord.utils.as_chunks(users, 500)

for chunk in user_chunks:
log.info(f"Upserting chunk of {len(chunk)}")
log.info("Upserting chunk of %d", len(chunk))
await models.User.bulk_upsert(chunk)

log.info("User upsert complete")
Expand Down Expand Up @@ -85,15 +86,14 @@ async def sync_channels(self, guild: discord.Guild) -> None:
log.info("Category synchronisation process complete, synchronising channels")

for channel in guild.channels:
if channel.category:
if channel.category.id in BotConfig.ignore_categories:
continue
if channel.category and channel.category.id in BotConfig.ignore_categories:
continue

if not isinstance(channel, discord.CategoryChannel):
category_id = str(channel.category.id) if channel.category else None
# Cast to bool so is_staff is False if channel.category is None
is_staff = channel.id in BotConfig.staff_channels or bool(
channel.category and channel.category.id in BotConfig.staff_categories
channel.category and channel.category.id in BotConfig.staff_categories,
)
if db_chan := await models.Channel.get(str(channel.id)):
await db_chan.update(
Expand Down Expand Up @@ -145,7 +145,7 @@ async def on_guild_channel_create(self, channel: discord.abc.GuildChannel) -> No
async def on_guild_channel_update(
self,
_before: discord.abc.GuildChannel,
channel: discord.abc.GuildChannel
channel: discord.abc.GuildChannel,
) -> None:
"""Sync the channels when one is updated."""
if channel.guild.id != BotConfig.guild_id:
Expand All @@ -172,7 +172,7 @@ async def on_thread_update(self, _before: discord.Thread, thread: discord.Thread
@commands.Cog.listener()
async def on_guild_available(self, guild: discord.Guild) -> None:
"""Synchronize the user table with the Discord users."""
log.info(f"Received guild available for {guild.id}")
log.info("Received guild available for %d", guild.id)

if guild.id != BotConfig.guild_id:
log.info("Guild was not the configured guild, discarding event")
Expand Down
Loading

0 comments on commit a526080

Please sign in to comment.