Compare commits
98
Commits
82-uv
...
81-sqlmodel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1e4ad20d | ||
|
|
354823ca1f | ||
|
|
45f17e09d3 | ||
|
|
530ca5b172 | ||
|
|
3dfdc10334 | ||
|
|
6c9353990c | ||
|
|
5412ab14a3 | ||
|
|
16cdc27bdb | ||
|
|
086f8b50cd | ||
|
|
a6a73cb46e | ||
|
|
57e19f3036 | ||
|
|
51c8483dad | ||
|
|
6b60c37726 | ||
|
|
3eea225931 | ||
|
|
0529dc6b4c | ||
|
|
865fee4ce2 | ||
|
|
052202d273 | ||
|
|
c09dd94284 | ||
|
|
76f4a9f37f | ||
|
|
f446f8e9cc | ||
|
|
dbd1d6dc00 | ||
|
|
66621ea2a0 | ||
|
|
6ae56bf8b2 | ||
|
|
f472c0419b | ||
|
|
a7532773c1 | ||
|
|
81ad4ba313 | ||
|
|
b221f1e438 | ||
|
|
1b08ba1355 | ||
|
|
e191df1a38 | ||
|
|
e35a2a8c8d | ||
|
|
d7046e4595 | ||
|
|
70037fb47a | ||
|
|
4ae3866e44 | ||
|
|
1349813f0d | ||
|
|
294b672fe2 | ||
|
|
72349a4cae | ||
|
|
f6e9d3c32e | ||
|
|
7c8455bf29 | ||
|
|
f4c4a0435b | ||
|
|
bbc63dd77a | ||
|
|
7ed3f0d613 | ||
|
|
eb6a8c7479 | ||
|
|
58b230afe6 | ||
|
|
fb8dfef55e | ||
|
|
487d174b6d | ||
|
|
abb50d71b4 | ||
|
|
7409e2fd2d | ||
|
|
6a67804937 | ||
|
|
4f4d76d151 | ||
|
|
f3985b5d21 | ||
|
|
142cc337a0 | ||
|
|
fd1d1c075e | ||
|
|
e3ffafcff2 | ||
|
|
855f3c1df8 | ||
|
|
a3371e9e8c | ||
|
|
a158248a35 | ||
|
|
886ac2f7ee | ||
|
|
f06033d2ec | ||
|
|
2ffc4126d2 | ||
|
|
4280ddd841 | ||
|
|
58c167bbe3 | ||
|
|
303e359c32 | ||
|
|
fb460ccc2c | ||
|
|
c5e4a94cd2 | ||
|
|
f95955dc7f | ||
|
|
dfea936aaa | ||
|
|
1df639499c | ||
|
|
be3ec007af | ||
|
|
e8839d2930 | ||
|
|
29eb21b286 | ||
|
|
b7439631d9 | ||
|
|
6b9d8f6ad9 | ||
|
|
c48fba9c85 | ||
|
|
7e3b57f4b6 | ||
|
|
889ba2fd6b | ||
|
|
8b5c954aa5 | ||
|
|
adc44d4466 | ||
|
|
969f380b08 | ||
|
|
10d7014c41 | ||
|
|
89e77a6671 | ||
|
|
c2ff656114 | ||
|
|
81e2957fa5 | ||
|
|
ba24a268ec | ||
|
|
70d262d947 | ||
|
|
6199350cca | ||
|
|
c5e2b39648 | ||
|
|
5db2b43d6a | ||
|
|
b9be9e5470 | ||
|
|
6700199a36 | ||
|
|
8f9821e9c9 | ||
|
|
83af20e04c | ||
|
|
f1dd3cbd3b | ||
|
|
d56251c0b9 | ||
|
|
667a600ec6 | ||
|
|
03a94ca6dd | ||
|
|
9da598e4d9 | ||
|
|
3a280696ca | ||
|
|
8825afdb46 |
@@ -5,19 +5,21 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
- '!main'
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
uses: https://github.com/papodaca/install-docker-action@v1
|
||||
- name: Log into container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.cheezers.org
|
||||
username: imcompiling
|
||||
@@ -3,19 +3,21 @@ name: build_latest
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
uses: https://github.com/papodaca/install-docker-action@v1
|
||||
- name: Log into container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.cheezers.org
|
||||
username: imcompiling
|
||||
@@ -9,13 +9,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
uses: https://github.com/papodaca/install-docker-action@v1
|
||||
- name: Log into container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.cheezers.org
|
||||
username: imcompiling
|
||||
@@ -105,7 +105,6 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
poetry.lock
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
Vendored
+4
-1
@@ -9,7 +9,10 @@
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "dakiny_bot",
|
||||
"justMyCode": true
|
||||
"justMyCode": true,
|
||||
"env": {
|
||||
"DEBUG": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+6
-1
@@ -3,10 +3,15 @@
|
||||
"aiofiles",
|
||||
"autofilled",
|
||||
"blurple",
|
||||
"CAKEDAY",
|
||||
"cakedays",
|
||||
"CLANLEADER",
|
||||
"Dakiny",
|
||||
"DISCORDMANAGER",
|
||||
"gitea",
|
||||
"highcouncil",
|
||||
"nextcord"
|
||||
"imcompiling",
|
||||
"nextcord",
|
||||
"ondelete"
|
||||
]
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
FROM python:3.12.3-slim
|
||||
FROM python:3.13.5-slim
|
||||
|
||||
# install dependencies
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
|
||||
|
||||
# install uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.6.5 /uv /uvx /bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.7.20 /uv /uvx /bin/
|
||||
|
||||
COPY . /home
|
||||
|
||||
|
||||
+18
-19
@@ -1,7 +1,6 @@
|
||||
"""Main file for bot."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
@@ -10,18 +9,12 @@ from dakiny_bot.classes.nominate_highcouncil_views import NominateHighCouncilVie
|
||||
from dakiny_bot.classes.nominate_views import NominateView
|
||||
from dakiny_bot.classes.voting_highcouncil_views import VoteHighcouncilView
|
||||
from dakiny_bot.classes.voting_views import VoteView
|
||||
from dakiny_bot.database import create_database
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
|
||||
from .DakinySettings import settings
|
||||
from .dakiny_settings import settings
|
||||
|
||||
birthday_list: list[int] = []
|
||||
|
||||
logger = logging.getLogger("nextcord")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
def run() -> None:
|
||||
"""Builds and runs bot."""
|
||||
@@ -38,19 +31,25 @@ def run() -> None:
|
||||
bot = commands.Bot(intents=intents)
|
||||
|
||||
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
|
||||
for folder in os.listdir("cogs"):
|
||||
bot.load_extension(f"cogs.{folder}")
|
||||
for folder in pathlib.Path("dakiny_bot/cogs").iterdir():
|
||||
bot.load_extension(f"dakiny_bot.cogs.{folder.name}")
|
||||
|
||||
@bot.listen()
|
||||
async def on_ready() -> None:
|
||||
"""When Discord is connected."""
|
||||
assert bot.user is not None # noqa: S101 documentation does this
|
||||
create_database # noqa: B018
|
||||
bot.add_view(NominateView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(NominateHighCouncilView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(VoteView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(VoteHighcouncilView(bot.get_guild(settings.params.guild_id)))
|
||||
print(f"{bot.user.name} has connected to Discord!") # noqa: T201 TODO make logging to print my stuff to console
|
||||
if bot.user is None:
|
||||
dakiny_logger.error("Discord connection failed.")
|
||||
return
|
||||
|
||||
if (guild := bot.get_guild(settings.params.guild_id)) is None:
|
||||
dakiny_logger.error("Could not find Dakiny guild object.")
|
||||
return
|
||||
|
||||
bot.add_view(NominateView(guild))
|
||||
bot.add_view(NominateHighCouncilView(guild))
|
||||
bot.add_view(VoteView(guild))
|
||||
bot.add_view(VoteHighcouncilView(guild))
|
||||
dakiny_logger.info("%s has connected to Discord!", bot.user.name)
|
||||
|
||||
# Run Discord bot
|
||||
bot.run(settings.params.discord_token)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Empty init file for classes module."""
|
||||
+155
-86
@@ -5,20 +5,18 @@ from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import nextcord
|
||||
from sqlalchemy import select, true, update
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.classes.nominate_highcouncil_views import NominateHighCouncilView
|
||||
from dakiny_bot.classes.nominate_views import NominateView
|
||||
from dakiny_bot.classes.voting_highcouncil_views import VoteHighcouncilView
|
||||
from dakiny_bot.classes.voting_views import VoteView
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.database.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember, ElectionInformation, Vote
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import (
|
||||
TEMPLATES_DIR,
|
||||
VoteType,
|
||||
@@ -70,7 +68,7 @@ class Election:
|
||||
list[str]: A list of message contents in the correct order. Must be a string because the message is too
|
||||
long.
|
||||
"""
|
||||
with Path(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8").open() as file:
|
||||
with Path(f"{TEMPLATES_DIR}/election_announcement.txt").open() as file:
|
||||
message = file.read()
|
||||
|
||||
next_election = get_datetime(
|
||||
@@ -98,7 +96,7 @@ class Election:
|
||||
Returns:
|
||||
str: Message to send.
|
||||
"""
|
||||
with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8").open() as file:
|
||||
with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt").open() as file:
|
||||
message = file.read()
|
||||
|
||||
return message.format(
|
||||
@@ -116,7 +114,7 @@ class Election:
|
||||
Returns:
|
||||
str: Message to send.
|
||||
"""
|
||||
with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8").open() as file:
|
||||
with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt").open() as file:
|
||||
message = file.read()
|
||||
|
||||
return message.format(
|
||||
@@ -131,7 +129,7 @@ class Election:
|
||||
Returns:
|
||||
str: Message to send
|
||||
"""
|
||||
with Path(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8").open() as file:
|
||||
with Path(f"{TEMPLATES_DIR}/vote_start.txt").open() as file:
|
||||
message = file.read()
|
||||
vote_end: datetime = get_datetime(settings.election_cycles[self.cycle].voting_end)
|
||||
|
||||
@@ -144,25 +142,37 @@ class Election:
|
||||
async def check_date(self) -> None:
|
||||
"""Function checks current date to see if something needs to be run."""
|
||||
today = datetime.now(tz=UTC)
|
||||
dakiny_logger.debug("Checking date.")
|
||||
|
||||
# check all dates if action is needed
|
||||
if today.date() == self.election_start.date():
|
||||
dakiny_logger.debug("Election start.")
|
||||
await self.election_start_action()
|
||||
elif today.date() == self.voting_start.date():
|
||||
dakiny_logger.debug("Voting start.")
|
||||
await self.voting_start_action()
|
||||
elif (self.voting_end - timedelta(3)).date() <= today.date() < self.voting_end.date():
|
||||
dakiny_logger.debug("Election reminder.")
|
||||
await self.election_reminder()
|
||||
elif today.date() == self.voting_end.date():
|
||||
dakiny_logger.debug("Voting end.")
|
||||
await self.voting_end_action()
|
||||
elif today.date() == self.highcouncil_election_start.date():
|
||||
dakiny_logger.debug("Highcouncil election start.")
|
||||
await self.highcouncil_election_start_action()
|
||||
elif today.date() == self.term_start.date():
|
||||
dakiny_logger.debug("Term start")
|
||||
await self.term_start_action()
|
||||
else:
|
||||
dakiny_logger.warning("Check date didn't do anything. This should never happen.")
|
||||
|
||||
async def election_start_action(self) -> None:
|
||||
"""Performs required actions for when the election cycle starts."""
|
||||
# create new channel for election
|
||||
parent: nextcord.TextChannel = self.guild.get_channel(settings.channel_ids.voting)
|
||||
parent = self.guild.get_channel(settings.channel_ids.voting)
|
||||
if not isinstance(parent, nextcord.TextChannel):
|
||||
dakiny_logger.error("Voting channel (id: %s) is not a text channel.", settings.channel_ids.voting)
|
||||
return
|
||||
quarter_name: str = get_election_name()
|
||||
|
||||
thread_msg: nextcord.Message = await parent.send(content=quarter_name)
|
||||
@@ -183,9 +193,9 @@ class Election:
|
||||
# create new entry into database.
|
||||
with Session(engine) as session:
|
||||
new_cycle = ElectionInformation(
|
||||
ElectionName=quarter_name,
|
||||
ThreadId=thread.id,
|
||||
CouncilNominationMsgId=nominate_msg.id,
|
||||
name=quarter_name,
|
||||
thread_id=thread.id,
|
||||
council_nomination_msg_id=nominate_msg.id,
|
||||
)
|
||||
session.add(new_cycle)
|
||||
session.commit()
|
||||
@@ -198,64 +208,98 @@ class Election:
|
||||
Removes write permission from the general clan member role.
|
||||
"""
|
||||
message = self.build_vote_start()
|
||||
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(col(ElectionInformation.name == get_election_name()))
|
||||
if (election_info := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Unable to find election %s", get_election_name())
|
||||
return
|
||||
if (election_thread := self.guild.get_thread(election_info.thread_id)) is None:
|
||||
dakiny_logger.error("Recorded thread id %s does not result in a thread.", election_info.thread_id)
|
||||
return
|
||||
|
||||
await election_thread.send(message)
|
||||
|
||||
vote_msg = await election_thread.send(
|
||||
build_votes_str(settings, self.guild),
|
||||
build_votes_str(self.guild),
|
||||
view=VoteView(self.guild),
|
||||
)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
|
||||
info = session.scalar(stmt)
|
||||
info.CouncilVoteMsgId = vote_msg.id
|
||||
election_info.council_vote_msg_id = vote_msg.id
|
||||
session.commit()
|
||||
|
||||
thread: nextcord.Thread = self.guild.get_thread(info.ThreadId)
|
||||
nominate_msg: nextcord.Message = await thread.fetch_message(info.CouncilNominationMsgId)
|
||||
if election_info.council_nomination_msg_id is None:
|
||||
dakiny_logger.error("Election doesn't have a nomination message set.")
|
||||
return
|
||||
|
||||
nominate_msg: nextcord.Message = await election_thread.fetch_message(
|
||||
election_info.council_nomination_msg_id
|
||||
)
|
||||
await nominate_msg.edit(view=None)
|
||||
|
||||
async def voting_end_action(self) -> None:
|
||||
"""Performs required actions for when voting time has ended."""
|
||||
message = self.build_highcouncil_election_announce()
|
||||
council_elect_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council_elect)
|
||||
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
|
||||
|
||||
nominate_msg: nextcord.Message = await election_thread.send(message, view=NominateHighCouncilView(self.guild))
|
||||
if (council_elect_role := self.guild.get_role(settings.role_ids.clan_council_elect)) is None:
|
||||
dakiny_logger.error("Clan council elect role not found (id: %s).", settings.role_ids.clan_council_elect)
|
||||
return
|
||||
|
||||
election_name = get_election_name()
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(col(ElectionInformation.name == election_name))
|
||||
if (info := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Unable to find election %s", election_name)
|
||||
return
|
||||
|
||||
if (election_thread := self.guild.get_thread(info.thread_id)) is None:
|
||||
dakiny_logger.error("Recorded thread id %s does not result in a thread.", info.thread_id)
|
||||
return
|
||||
|
||||
nominate_msg: nextcord.Message = await election_thread.send(
|
||||
message, view=NominateHighCouncilView(self.guild)
|
||||
)
|
||||
|
||||
# remove vote button from vote message
|
||||
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == election_name)
|
||||
info = session.scalar(stmt)
|
||||
vote_msg: nextcord.Message = await election_thread.fetch_message(info.CouncilVoteMsgId)
|
||||
if not info.council_vote_msg_id:
|
||||
dakiny_logger.error("Council vote message id not set.")
|
||||
return
|
||||
vote_msg: nextcord.Message = await election_thread.fetch_message(info.council_vote_msg_id)
|
||||
await vote_msg.edit(view=None)
|
||||
|
||||
# add the highcouncil nomination message to election information
|
||||
info.HighCouncilNominationMsgId = nominate_msg.id
|
||||
info.high_council_nomination_msg_id = nominate_msg.id
|
||||
session.commit()
|
||||
|
||||
# add the highcouncil role to everyone with 3 or more votes. set elected to true in database
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.type == VoteType.COUNCIL))
|
||||
)
|
||||
vote_count = collections.Counter(session.scalars(stmt))
|
||||
|
||||
for user_id, votes in vote_count.items():
|
||||
if user_id is None:
|
||||
# Abstained votes, ignore
|
||||
continue
|
||||
if votes >= REQUIRED_VOTES_TO_BE_ELECTED:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == user_id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == user_id))
|
||||
)
|
||||
council_member = session.scalar(stmt)
|
||||
council_member.Elected = True
|
||||
if (council_member := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error(
|
||||
"Council member (id: %s) for election %s not found.", user_id, election_name
|
||||
)
|
||||
return
|
||||
council_member.elected = True
|
||||
session.commit()
|
||||
|
||||
member: nextcord.Member = self.guild.get_member(user_id)
|
||||
if (member := self.guild.get_member(user_id)) is None:
|
||||
dakiny_logger.error("Member (id: %s) not found in guild.", user_id)
|
||||
return
|
||||
|
||||
await member.add_roles(
|
||||
council_elect_role,
|
||||
@@ -265,42 +309,58 @@ class Election:
|
||||
async def highcouncil_election_start_action(self) -> None:
|
||||
"""Performs required actions for when the highcouncil is elected."""
|
||||
message = self.build_highcouncil_vote_start()
|
||||
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(col(ElectionInformation.name == get_election_name()))
|
||||
if (info := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Unknown election name %s.", get_election_name())
|
||||
return
|
||||
|
||||
if (election_thread := self.guild.get_thread(info.thread_id)) is None:
|
||||
dakiny_logger.error("Recorded thread id %s does not result in a thread.", info.thread_id)
|
||||
return
|
||||
|
||||
await election_thread.send(message)
|
||||
vote_msg = await election_thread.send(
|
||||
build_highcouncil_votes_str(self.guild),
|
||||
view=VoteHighcouncilView(self.guild),
|
||||
)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
|
||||
info = session.scalar(stmt)
|
||||
info.HighCouncilVoteMsgId = vote_msg.id
|
||||
info.high_council_vote_msg_id = vote_msg.id
|
||||
session.commit()
|
||||
|
||||
# remove highcouncil nomination view
|
||||
nomination_msg: nextcord.Message = await election_thread.fetch_message(info.HighCouncilNominationMsgId)
|
||||
if info.high_council_nomination_msg_id is None:
|
||||
dakiny_logger.error("Election doesn't have a nomination message set.")
|
||||
return
|
||||
|
||||
nomination_msg: nextcord.Message = await election_thread.fetch_message(info.high_council_nomination_msg_id)
|
||||
await nomination_msg.edit(view=None)
|
||||
|
||||
async def term_start_action(self) -> None:
|
||||
"""Marks the election as over."""
|
||||
self.election_over = True
|
||||
|
||||
elect_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council_elect)
|
||||
council_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council)
|
||||
if (elect_role := self.guild.get_role(settings.role_ids.clan_council_elect)) is None:
|
||||
dakiny_logger.error("Cannot find Elect Role.")
|
||||
return
|
||||
if (council_role := self.guild.get_role(settings.role_ids.clan_council)) is None:
|
||||
dakiny_logger.error("Cannot find Council Role.")
|
||||
return
|
||||
|
||||
election_name = get_election_name()
|
||||
|
||||
with Session(engine) as session:
|
||||
# role management
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.Elected == true())
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.elected))
|
||||
)
|
||||
|
||||
for member_id in session.scalars(stmt):
|
||||
member = self.guild.get_member(member_id)
|
||||
if (member := self.guild.get_member(member_id)) is None:
|
||||
dakiny_logger.warning("Cannot find member (id: %s) to manage roles.", member_id)
|
||||
continue
|
||||
await member.remove_roles(
|
||||
elect_role,
|
||||
reason=f"{member.display_name} no longer needs the elect role.",
|
||||
@@ -313,35 +373,36 @@ class Election:
|
||||
# announce high council members
|
||||
# ties are undefined behavior.
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.type == VoteType.DISCORDMANAGER))
|
||||
)
|
||||
dm_votes = collections.Counter(session.scalars(stmt))
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.Type == VoteType.CLANLEADER)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.type == VoteType.CLANLEADER))
|
||||
)
|
||||
cl_votes = collections.Counter(session.scalars(stmt))
|
||||
cl_votes.most_common()
|
||||
|
||||
try:
|
||||
dm_winner = max(dm_votes, key=dm_votes.get)
|
||||
dm_winner = dm_votes.most_common(1).pop()[0]
|
||||
stmt = (
|
||||
update(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == dm_winner)
|
||||
update(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == dm_winner))
|
||||
.values(DiscordManager=True)
|
||||
)
|
||||
session.execute(stmt)
|
||||
except ValueError:
|
||||
dm_winner = "No winner"
|
||||
try:
|
||||
cl_winner = max(cl_votes, key=cl_votes.get)
|
||||
cl_winner = cl_votes.most_common(1).pop()[0]
|
||||
stmt = (
|
||||
update(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == cl_winner)
|
||||
update(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == cl_winner))
|
||||
.values(ClanLeader=True)
|
||||
)
|
||||
session.execute(stmt)
|
||||
@@ -350,14 +411,21 @@ class Election:
|
||||
|
||||
session.commit()
|
||||
|
||||
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
|
||||
stmt = select(ElectionInformation).where(col(ElectionInformation.name == election_name))
|
||||
if (info := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Could not find information for this election (%s).", election_name)
|
||||
return
|
||||
|
||||
if (election_thread := self.guild.get_thread(info.thread_id)) is None:
|
||||
dakiny_logger.error("Recorded thread id %s does not result in a thread.", info.thread_id)
|
||||
return
|
||||
|
||||
if info.high_council_vote_msg_id is None:
|
||||
dakiny_logger.error("No recorded High Council Voting Message.")
|
||||
return
|
||||
|
||||
# clear view from vote message
|
||||
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
|
||||
ElectionInformation.ElectionName == get_election_name()
|
||||
)
|
||||
vote_msg_id = session.scalar(stmt)
|
||||
vote_msg: nextcord.Message = await election_thread.fetch_message(vote_msg_id)
|
||||
vote_msg: nextcord.Message = await election_thread.fetch_message(info.high_council_vote_msg_id)
|
||||
await vote_msg.edit(view=None)
|
||||
|
||||
highcouncil_msg: str = f"Congratulations for our high council members!\nClan Leader: <@{cl_winner}>\n"
|
||||
@@ -369,22 +437,23 @@ class Election:
|
||||
await election_thread.edit(archived=True, locked=True)
|
||||
await edit_clan_council_info(self.guild)
|
||||
|
||||
def get_election_info(self) -> ElectionInformation:
|
||||
"""
|
||||
Gets information for current election.
|
||||
|
||||
Returns:
|
||||
ElectionInformation: The information from the database about the current election.
|
||||
"""
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
|
||||
return session.scalar(stmt)
|
||||
|
||||
async def election_reminder(self) -> None:
|
||||
"""Sends a message to general chat reminder clan about election."""
|
||||
general: nextcord.TextChannel = self.guild.get_channel(settings.channel_ids.general)
|
||||
general = self.guild.get_channel(settings.channel_ids.general)
|
||||
|
||||
if not isinstance(general, nextcord.TextChannel):
|
||||
dakiny_logger.error(
|
||||
"Recorded general channel (id: %s) is not a text channel.", settings.channel_ids.general
|
||||
)
|
||||
return
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(col(ElectionInformation.name == get_election_name()))
|
||||
if (info := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Could not find election %s.", get_election_name())
|
||||
return
|
||||
|
||||
message = f"Reminder to <@&{settings.role_ids.clan_member}>! Clan elections are happening! Head over "
|
||||
message += f"to <#{self.get_election_info().ThreadId}> to cast your vote!"
|
||||
message += f"to <#{info.thread_id}> to cast your vote!"
|
||||
|
||||
await general.send(content=message)
|
||||
|
||||
@@ -4,9 +4,12 @@ import nextcord
|
||||
from nextcord.interactions import Interaction
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import (
|
||||
NOMINEE_SPLITTER,
|
||||
decorate_cl_nomination,
|
||||
@@ -53,26 +56,35 @@ class NominateDMModal(nextcord.ui.Modal):
|
||||
interaction (Interaction): Interaction with user.
|
||||
|
||||
"""
|
||||
speech = decorate_dm_nomination(interaction.user.id, self.speech.value)
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
speech = decorate_dm_nomination(interaction.user.id, self.speech.value if self.speech.value else "")
|
||||
|
||||
with Session(engine) as session:
|
||||
# user already in table
|
||||
election_name = get_election_name()
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == self.usr_id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == self.usr_id))
|
||||
)
|
||||
council_elect = session.scalar(stmt)
|
||||
|
||||
if not self.usr_nominated:
|
||||
if council_elect is None:
|
||||
dakiny_logger.error("Did not find member (id: %s) in election %s.", self.usr_id, election_name)
|
||||
return
|
||||
|
||||
if not self.usr_nominated or council_elect.discord_manager_speech_id is None:
|
||||
msg = await interaction.channel.send(speech)
|
||||
|
||||
council_elect.DiscordManagerSpeechId = msg.id
|
||||
council_elect.discord_manager_speech_id = msg.id
|
||||
session.commit()
|
||||
else:
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.DiscordManagerSpeechId)
|
||||
await msg.edit(content=decorate_dm_nomination(interaction.user.id, self.speech.value))
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.discord_manager_speech_id)
|
||||
await msg.edit(content=speech)
|
||||
|
||||
|
||||
class NominateCLModal(nextcord.ui.Modal):
|
||||
@@ -113,26 +125,35 @@ class NominateCLModal(nextcord.ui.Modal):
|
||||
interaction (Interaction): Interaction with user.
|
||||
|
||||
"""
|
||||
speech = decorate_cl_nomination(interaction.user.id, self.speech.value)
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
speech = decorate_cl_nomination(interaction.user.id, self.speech.value if self.speech.value else "")
|
||||
|
||||
with Session(engine) as session:
|
||||
# user already in table
|
||||
election_name = get_election_name()
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == self.usr_id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == self.usr_id))
|
||||
)
|
||||
council_elect = session.scalar(stmt)
|
||||
|
||||
if not self.usr_nominated:
|
||||
if council_elect is None:
|
||||
dakiny_logger.error("Did not find member (id: %s) in election %s.", self.usr_id, election_name)
|
||||
return
|
||||
|
||||
if not self.usr_nominated or council_elect.clan_leader_speech_id is None:
|
||||
msg = await interaction.channel.send(speech)
|
||||
|
||||
council_elect.ClanLeaderSpeechId = msg.id
|
||||
council_elect.clan_leader_speech_id = msg.id
|
||||
session.commit()
|
||||
else:
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.ClanLeaderSpeechId)
|
||||
await msg.edit(content=decorate_cl_nomination(interaction.user.id, self.speech.value))
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.clan_leader_speech_id)
|
||||
await msg.edit(content=speech)
|
||||
|
||||
|
||||
class NominateHighCouncilView(nextcord.ui.View):
|
||||
@@ -158,8 +179,16 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
# enforce council elect only
|
||||
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
||||
if (
|
||||
isinstance(interaction.user, nextcord.User)
|
||||
or self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles
|
||||
):
|
||||
await interaction.response.send_message(
|
||||
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
|
||||
ephemeral=True,
|
||||
@@ -171,16 +200,15 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == interaction.user.id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == interaction.user.id))
|
||||
)
|
||||
obj = session.scalar(stmt)
|
||||
council_elect = session.scalar(stmt)
|
||||
|
||||
# user needs to exist to get here anyways...
|
||||
if obj.DiscordManagerSpeechId is not None:
|
||||
if council_elect is not None and council_elect.discord_manager_speech_id is not None:
|
||||
usr_nominated = True
|
||||
msg = await interaction.channel.fetch_message(obj.DiscordManagerSpeechId)
|
||||
msg = await interaction.channel.fetch_message(council_elect.discord_manager_speech_id)
|
||||
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
|
||||
|
||||
await interaction.response.send_modal(NominateDMModal(interaction.user.id, usr_nominated, speech_content))
|
||||
@@ -201,8 +229,16 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
# enforce council elect only
|
||||
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
||||
if (
|
||||
isinstance(interaction.user, nextcord.User)
|
||||
or self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles
|
||||
):
|
||||
await interaction.response.send_message(
|
||||
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
|
||||
ephemeral=True,
|
||||
@@ -214,16 +250,15 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == interaction.user.id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == interaction.user.id))
|
||||
)
|
||||
obj = session.scalar(stmt)
|
||||
council_elect = session.scalar(stmt)
|
||||
|
||||
# user needs to exist to get here anyways...
|
||||
if obj.ClanLeaderSpeechId is not None:
|
||||
if council_elect is not None and council_elect.clan_leader_speech_id is not None:
|
||||
usr_nominated = True
|
||||
msg = await interaction.channel.fetch_message(obj.ClanLeaderSpeechId)
|
||||
msg = await interaction.channel.fetch_message(council_elect.clan_leader_speech_id)
|
||||
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
|
||||
|
||||
await interaction.response.send_modal(NominateCLModal(interaction.user.id, usr_nominated, speech_content))
|
||||
|
||||
@@ -4,9 +4,12 @@ import nextcord
|
||||
from nextcord.interactions import Interaction
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name
|
||||
|
||||
|
||||
@@ -48,31 +51,45 @@ class NominateModal(nextcord.ui.Modal):
|
||||
interaction (Interaction): Interaction with user.
|
||||
|
||||
"""
|
||||
speech = decorate_nomination(interaction.user.id, self.speech.value)
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
speech = decorate_nomination(interaction.user.id, self.speech.value if self.speech.value else "")
|
||||
election_name = get_election_name()
|
||||
|
||||
with Session(engine) as session:
|
||||
if not self.usr_nominated:
|
||||
msg = await interaction.channel.send(speech)
|
||||
|
||||
council_member = CouncilMembers(
|
||||
ElectionName=election_name,
|
||||
MemberId=interaction.user.id,
|
||||
SpeechMsgId=msg.id,
|
||||
council_member = CouncilMember(
|
||||
election_name=election_name,
|
||||
member_id=interaction.user.id,
|
||||
speech_msg_id=msg.id,
|
||||
)
|
||||
session.add(council_member)
|
||||
session.commit()
|
||||
else:
|
||||
# user already nominated, just need to update the election speech
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == self.usr_id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == self.usr_id))
|
||||
)
|
||||
obj = session.scalar(stmt)
|
||||
council_elect = session.scalar(stmt)
|
||||
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(obj.SpeechMsgId)
|
||||
await msg.edit(content=decorate_nomination(interaction.user.id, self.speech.value))
|
||||
if council_elect is None:
|
||||
dakiny_logger.error("Did not find member (id: %s) in election %s.", self.usr_id, election_name)
|
||||
return
|
||||
|
||||
if council_elect.speech_msg_id is None:
|
||||
msg = await interaction.channel.send(speech)
|
||||
council_elect.speech_msg_id = msg.id
|
||||
session.commit()
|
||||
else:
|
||||
msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.speech_msg_id)
|
||||
await msg.edit(content=speech)
|
||||
|
||||
|
||||
class NominateView(nextcord.ui.View):
|
||||
@@ -98,8 +115,16 @@ class NominateView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if interaction.user is None or not isinstance(interaction.channel, nextcord.TextChannel):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
return
|
||||
|
||||
# enforce members only
|
||||
if self.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
|
||||
if (
|
||||
isinstance(interaction.user, nextcord.User)
|
||||
or self.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles
|
||||
):
|
||||
await interaction.response.send_message(
|
||||
f"You must have the <@&{settings.role_ids.clan_member}> role to vote.",
|
||||
ephemeral=True,
|
||||
@@ -111,14 +136,14 @@ class NominateView(nextcord.ui.View):
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.MemberId == interaction.user.id)
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.member_id == interaction.user.id))
|
||||
)
|
||||
obj = session.scalar(stmt)
|
||||
if obj is not None:
|
||||
if obj is not None and obj.speech_msg_id is not None:
|
||||
usr_nominated = True
|
||||
msg = await interaction.channel.fetch_message(obj.SpeechMsgId)
|
||||
msg = await interaction.channel.fetch_message(obj.speech_msg_id)
|
||||
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
|
||||
|
||||
await interaction.response.send_modal(NominateModal(interaction.user.id, usr_nominated, speech_content))
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
[tool.ruff]
|
||||
# Extend the `pyproject.toml` file in the parent directory...
|
||||
extend = "../pyproject.toml"
|
||||
extend = "../../pyproject.toml"
|
||||
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = ["gitea_webhook.py"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
# ignore implicit-namespace-package in this directory.
|
||||
ignore = ["INP001"]
|
||||
|
||||
@@ -5,14 +5,12 @@ from nextcord import SelectOption
|
||||
from nextcord.interactions import Interaction
|
||||
from sqlalchemy import exc, select, update
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.database.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember, ElectionInformation, Vote
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import VoteType, build_highcouncil_votes_str, get_election_name
|
||||
|
||||
|
||||
@@ -38,20 +36,32 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
Args:
|
||||
interaction (Interaction): Interaction with this callback.
|
||||
"""
|
||||
if (
|
||||
interaction.user is None
|
||||
or not isinstance(interaction.channel, nextcord.TextChannel)
|
||||
or interaction.guild is None
|
||||
):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
# if no guild, is problem because should only be in guild.
|
||||
return
|
||||
|
||||
election_name: str = get_election_name()
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
|
||||
ElectionInformation.ElectionName == election_name
|
||||
stmt = select(col(ElectionInformation.high_council_vote_msg_id)).where(
|
||||
col(ElectionInformation.name == election_name)
|
||||
)
|
||||
vote_msg_id = session.scalar(stmt)
|
||||
if (vote_msg_id := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Unable to find a Highcouncil Vote Message ID in database.")
|
||||
return
|
||||
|
||||
voted_for = None if self.values[0] == "Abstain" else int(self.values[0])
|
||||
|
||||
new_vote = Votes(
|
||||
ElectionName=election_name,
|
||||
VoterId=interaction.user.id,
|
||||
VotedForId=voted_for,
|
||||
Type=self.election_type,
|
||||
new_vote = Vote(
|
||||
election_name=election_name,
|
||||
voter_id=interaction.user.id,
|
||||
voted_for_id=voted_for,
|
||||
type=self.election_type,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -62,10 +72,10 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
# user already has a vote in the election and election type
|
||||
session.rollback()
|
||||
stmt = (
|
||||
update(Votes)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == self.election_type)
|
||||
update(Vote)
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.voter_id == interaction.user.id))
|
||||
.where(col(Vote.type == self.election_type))
|
||||
.values(VotedForId=voted_for)
|
||||
)
|
||||
session.execute(stmt)
|
||||
@@ -104,6 +114,10 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if not isinstance(interaction.user, nextcord.Member):
|
||||
# the user should only be a member of the guild.
|
||||
return
|
||||
|
||||
# enforce council elect only
|
||||
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
||||
await interaction.response.send_message(
|
||||
@@ -116,32 +130,29 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.CLANLEADER)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.voter_id == interaction.user.id))
|
||||
.where(col(Vote.type == VoteType.CLANLEADER))
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.clan_leader_speech_id is not None))
|
||||
)
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
label=(f"{member.display_name} ☑️" if voted_for == nominee else f"{member.display_name}"),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
)
|
||||
if (member := self.guild.get_member(nominee)) is not None
|
||||
]
|
||||
options.append(
|
||||
SelectOption(
|
||||
@@ -176,6 +187,10 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if not isinstance(interaction.user, nextcord.Member):
|
||||
# the user should only be a member of the guild.
|
||||
return
|
||||
|
||||
# enforce council elect only
|
||||
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
||||
await interaction.response.send_message(
|
||||
@@ -188,32 +203,29 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.voter_id == interaction.user.id))
|
||||
.where(col(Vote.type == VoteType.DISCORDMANAGER))
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.discord_manager_speech_id is not None))
|
||||
)
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
label=(f"{member.display_name} ☑️" if voted_for == nominee else f"{member.display_name}"),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
)
|
||||
if (member := self.guild.get_member(nominee)) is not None
|
||||
]
|
||||
options.append(
|
||||
SelectOption(
|
||||
|
||||
@@ -5,14 +5,12 @@ from nextcord import SelectOption
|
||||
from nextcord.interactions import Interaction
|
||||
from sqlalchemy import exc, select, update
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.database.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember, ElectionInformation, Vote
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import VoteType, build_votes_str, get_election_name
|
||||
|
||||
|
||||
@@ -37,18 +35,32 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
Args:
|
||||
interaction (Interaction): Interaction with this callback.
|
||||
"""
|
||||
if (
|
||||
interaction.user is None
|
||||
or not isinstance(interaction.channel, nextcord.TextChannel)
|
||||
or interaction.guild is None
|
||||
):
|
||||
# ignore none... Don't know what situation this is.
|
||||
# if not textchannel, don't know how callback happened
|
||||
# if no guild, is problem because should only be in guild.
|
||||
return
|
||||
|
||||
election_name = get_election_name()
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation.CouncilVoteMsgId).where(ElectionInformation.ElectionName == election_name)
|
||||
vote_msg_id = session.scalar(stmt)
|
||||
stmt = select(col(ElectionInformation.council_vote_msg_id)).where(
|
||||
col(ElectionInformation.name == election_name)
|
||||
)
|
||||
if (vote_msg_id := session.scalar(stmt)) is None:
|
||||
dakiny_logger.error("Unable to find a Vote Message ID in database.")
|
||||
return
|
||||
|
||||
voted_for = None if self.values[0] == "Abstain" else int(self.values[0])
|
||||
|
||||
new_vote = Votes(
|
||||
ElectionName=election_name,
|
||||
VoterId=interaction.user.id,
|
||||
VotedForId=voted_for,
|
||||
Type=VoteType.COUNCIL,
|
||||
new_vote = Vote(
|
||||
election_name=election_name,
|
||||
voter_id=interaction.user.id,
|
||||
voted_for_id=voted_for,
|
||||
type=VoteType.COUNCIL,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -58,16 +70,16 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
except exc.IntegrityError:
|
||||
session.rollback()
|
||||
stmt = (
|
||||
update(Votes)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
update(Vote)
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.voter_id == interaction.user.id))
|
||||
.where(col(Vote.type == VoteType.COUNCIL))
|
||||
.values(VotedForId=voted_for)
|
||||
)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
vote_message_str = build_votes_str(settings, interaction.guild)
|
||||
vote_message_str = build_votes_str(interaction.guild)
|
||||
vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id)
|
||||
await vote_msg.edit(content=vote_message_str)
|
||||
|
||||
@@ -95,6 +107,10 @@ class VoteView(nextcord.ui.View):
|
||||
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
|
||||
interaction (nextcord.Interaction): Interaction of user who pressed the button
|
||||
"""
|
||||
if not isinstance(interaction.user, nextcord.Member):
|
||||
# the user should only be a member of the guild.
|
||||
return
|
||||
|
||||
# enforce members only
|
||||
if self.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
|
||||
await interaction.response.send_message(
|
||||
@@ -107,28 +123,25 @@ class VoteView(nextcord.ui.View):
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionName == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
select(col(Vote.voted_for_id))
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.voter_id == interaction.user.id))
|
||||
.where(col(Vote.type == VoteType.COUNCIL))
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
stmt = select(CouncilMembers.MemberId).where(CouncilMembers.ElectionName == election_name)
|
||||
stmt = select(col(CouncilMember.member_id)).where(col(CouncilMember.election_name == election_name))
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
label=(f"{member.display_name} ☑️" if voted_for == nominee else f"{member.display_name}"),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(),
|
||||
key=lambda nominee: self.guild.get_member(nominee).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
)
|
||||
if (member := self.guild.get_member(nominee)) is not None
|
||||
]
|
||||
options.append(
|
||||
SelectOption(
|
||||
|
||||
@@ -6,10 +6,12 @@ from typing import TYPE_CHECKING
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from git import Repo
|
||||
from git.exc import GitCommandError
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.classes.gitea_webhook import GiteaWebhook
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import REPO_DIR, edit_clan_council_info, update_clan_messages
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -55,7 +57,12 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
|
||||
template_updates = {file.split("/")[1] for file in modified_files if "message_templates" in file}
|
||||
channel_updates = {file.split("/")[1] for file in modified_files if file.split("/")[1] not in template_updates}
|
||||
|
||||
try:
|
||||
self.origin.pull()
|
||||
except GitCommandError:
|
||||
# I think the git connection timed out. Get a new one.
|
||||
dakiny_logger.info("Refreshing git connection.")
|
||||
self.repo_setup()
|
||||
guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id)
|
||||
|
||||
await update_clan_messages(guild, channel_updates)
|
||||
|
||||
@@ -4,7 +4,7 @@ import nextcord
|
||||
from discord import TextInputStyle
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import nextcord
|
||||
from nextcord.ext import commands
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.sql_data_classes import Presences, engine
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.utils import GUILDS
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import nextcord
|
||||
from nextcord.ext import application_checks, commands
|
||||
|
||||
from dakiny_bot.classes.election import Election
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.cogs.loops import daily_loop
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.utils import GUILDS, get_active_election_cycle, get_quarter_name
|
||||
|
||||
ME = 206602713511886849
|
||||
@@ -119,11 +120,8 @@ class TestCog(commands.Cog, name="Test commands."):
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction with the message.
|
||||
"""
|
||||
cycle = Election(
|
||||
get_active_election_cycle(),
|
||||
self.bot.get_guild(settings.params.guild_id),
|
||||
)
|
||||
await cycle.check_date()
|
||||
dl = daily_loop.DailyLoop(self.bot)
|
||||
await dl.daily_loop()
|
||||
await interaction.send("Test command sent.", ephemeral=True)
|
||||
|
||||
@test.subcommand()
|
||||
|
||||
@@ -11,8 +11,9 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from dakiny_bot.app import birthday_list
|
||||
from dakiny_bot.classes.election import Election
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.database.sql_data_classes import UserBirthday, engine
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
from dakiny_bot.utils import get_active_election_cycle
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -20,6 +21,8 @@ if TYPE_CHECKING:
|
||||
|
||||
KANDY = 239547993106284544
|
||||
|
||||
IGNORE_CAKEDAY = [266378407682506754]
|
||||
|
||||
|
||||
class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||
"""Class for performing daily functions."""
|
||||
@@ -41,8 +44,10 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||
an active election, runs the check_date() function for the election to perform actions. Finally checks if the
|
||||
election is over to delete the current election object.
|
||||
"""
|
||||
dakiny_logger.debug("Daily loop. Current election: %s.", self.current_election)
|
||||
if self.current_election is None:
|
||||
self.current_election = self.get_election()
|
||||
dakiny_logger.debug("Current election: %s.", self.current_election)
|
||||
|
||||
if self.current_election is not None:
|
||||
await self.current_election.check_date()
|
||||
@@ -76,13 +81,17 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||
Election: Current election cycle, None if there is no election happening.
|
||||
"""
|
||||
cycle = get_active_election_cycle()
|
||||
dakiny_logger.debug("Current cycle: %s.", cycle)
|
||||
|
||||
# prevent exception from using cycle as an Int in an array
|
||||
if cycle is None:
|
||||
dakiny_logger.info("Cycle was None. Returning without an election.")
|
||||
return None
|
||||
|
||||
election = Election(cycle, self.bot.get_guild(settings.params.guild_id))
|
||||
|
||||
dakiny_logger.debug("Election active: %s", election.active)
|
||||
|
||||
return election if election.active else None
|
||||
|
||||
def update_birthdays(self) -> None:
|
||||
@@ -112,7 +121,11 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||
announce_channel: nextcord.TextChannel = guild.get_channel(settings.channel_ids.general)
|
||||
|
||||
cakedays: list[nextcord.Member] = [
|
||||
human for human in guild.humans if human.joined_at.month == today.month and human.joined_at.day == today.day
|
||||
human
|
||||
for human in guild.humans
|
||||
if human.joined_at.month == today.month
|
||||
and human.joined_at.day == today.day
|
||||
and human.id not in IGNORE_CAKEDAY
|
||||
]
|
||||
|
||||
for member in cakedays:
|
||||
|
||||
@@ -4,6 +4,7 @@ import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.app import birthday_list
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
|
||||
|
||||
class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||
@@ -16,7 +17,15 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||
async def on_message(self, message: nextcord.Message) -> None:
|
||||
"""Listener for message creation. Adds score and attachments to submission."""
|
||||
# don't want to process any messages from bot.
|
||||
if message.author.id == self.bot.user.id:
|
||||
if message.author.id == self.bot.user.id: # pyright: ignore[reportOptionalMemberAccess]
|
||||
return
|
||||
|
||||
# Ban all members who message in this channel. Everyone in guild is a member, so shouldn't miss a ban
|
||||
# due to message.author not being a nextcord.Member object.
|
||||
if message.channel.id == settings.channel_ids.ban and isinstance(message.author, nextcord.Member):
|
||||
await message.author.ban(
|
||||
delete_message_seconds=30 * 60, reason="Probable spam. The ban channel was messaged."
|
||||
)
|
||||
return
|
||||
|
||||
await birthday_action(message)
|
||||
@@ -31,7 +40,7 @@ def setup(bot: commands.Bot) -> None:
|
||||
|
||||
async def birthday_action(message: nextcord.Message) -> None:
|
||||
"""
|
||||
Checks a message's auther and mentions to see if they are in the birthday list.
|
||||
Checks a message's author and mentions to see if they are in the birthday list.
|
||||
|
||||
Args:
|
||||
message (nextcord.Message): The message to check for a birthday person.
|
||||
|
||||
@@ -17,6 +17,7 @@ class ChannelIds:
|
||||
event_post: int
|
||||
general: int
|
||||
voting: int
|
||||
ban: int
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -83,4 +84,5 @@ class DakinySettings(YAMLWizard):
|
||||
self.to_yaml_file(CONFIG_LOCATION)
|
||||
|
||||
|
||||
settings = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
||||
tmp = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
||||
settings: DakinySettings = tmp[0] if isinstance(tmp, list) else tmp
|
||||
@@ -0,0 +1 @@
|
||||
"""Module controls connection to Dakiny bot database."""
|
||||
@@ -1,13 +0,0 @@
|
||||
"""Small script to create the database if it doesn't already exist."""
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
if not Path("database/dakiny.db").exists():
|
||||
with sqlite3.connect("database/dakiny.db") as con:
|
||||
cur = con.cursor()
|
||||
|
||||
with Path("database_setup/database.sql", encoding="utf-8").open() as file:
|
||||
database_script = file.read()
|
||||
cur.executescript(database_script)
|
||||
con.commit()
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Simple file to set up the sqlmodel connection to database."""
|
||||
|
||||
from sqlmodel import SQLModel, create_engine
|
||||
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
|
||||
from . import models # noqa: F401 - Need this import for creating the models.
|
||||
|
||||
url = "sqlite:///database/dakiny.db"
|
||||
dakiny_logger.info("Connected to local dev database.")
|
||||
engine = create_engine(url)
|
||||
|
||||
SQLModel.metadata.create_all(engine)
|
||||
@@ -0,0 +1,218 @@
|
||||
"""File holds all model definitions of SQL tables."""
|
||||
|
||||
from sqlalchemy.dialects.mysql import BIGINT, BOOLEAN, INTEGER, VARCHAR
|
||||
from sqlmodel import Column, Field, ForeignKey, Relationship, SQLModel
|
||||
|
||||
|
||||
class ElectionInformation(SQLModel, table=True):
|
||||
"""
|
||||
Represents all required information for an election.
|
||||
|
||||
Attributes:
|
||||
name: Name of the election.
|
||||
thread_id: Thread of where all the election related messages take place.
|
||||
council_vote_msg_id: Message where the council vote is presented.
|
||||
high_council_vote_msg_id: Message where the high council vote is presented.
|
||||
council_nomination_msg_id: Message where council nomination button is held.
|
||||
high_council_nomination_msg_id: Message where high council nomination button is held.
|
||||
council_members: List of council members who ran during this election.
|
||||
votes: All votes cast during this election.
|
||||
"""
|
||||
|
||||
__tablename__: str = "ElectionInformation"
|
||||
|
||||
name: str = Field(sa_column=Column("ElectionName", VARCHAR, primary_key=True, unique=True), max_length=30)
|
||||
thread_id: int = Field(sa_column=Column("ThreadId", BIGINT(unsigned=True), nullable=False))
|
||||
council_vote_msg_id: int | None = Field(
|
||||
sa_column=Column("CouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
high_council_vote_msg_id: int | None = Field(
|
||||
sa_column=Column("HighCouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
council_nomination_msg_id: int | None = Field(
|
||||
sa_column=Column("CouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
high_council_nomination_msg_id: int | None = Field(
|
||||
sa_column=Column("HighCouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
|
||||
council_members: list["CouncilMember"] = Relationship(back_populates="election", cascade_delete=True)
|
||||
votes: list["Vote"] = Relationship(back_populates="election", cascade_delete=True)
|
||||
|
||||
|
||||
class CouncilMember(SQLModel, table=True):
|
||||
"""
|
||||
Represents members involved in a given election.
|
||||
|
||||
Records speech message ids for general election and high council position. Booleans record if the user was elected
|
||||
to a council/high council position.
|
||||
|
||||
Attributes:
|
||||
member_id: User that is running for council's discord id.
|
||||
speech_msg_id: Message id of user's speech.
|
||||
elected: Whether the user got elected to council.
|
||||
discord_manager: Whether the user got elected to the Discord Manager position.
|
||||
clan_leader: Whether the user got elected to the Clan Leader position.
|
||||
discord_manager_speech_id: Message id of the user's Discord Manager speech.
|
||||
clan_leader_speech_id: Message id of the user's Clan Leader speech.
|
||||
election: The election this user object is for.
|
||||
votes: A list of votes for this user.
|
||||
"""
|
||||
|
||||
__tablename__: str = "CouncilMembers"
|
||||
|
||||
election_name: str = Field(
|
||||
sa_column=Column(
|
||||
"ElectionName",
|
||||
VARCHAR,
|
||||
ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
max_length=30,
|
||||
)
|
||||
member_id: int = Field(sa_column=Column("MemberId", BIGINT(unsigned=True), primary_key=True, nullable=False))
|
||||
speech_msg_id: int | None = Field(
|
||||
sa_column=Column("SpeechMsgId", BIGINT(unsigned=True), nullable=False), default=None
|
||||
)
|
||||
elected: bool = Field(sa_column=Column("Elected", BOOLEAN), default=False)
|
||||
discord_manager: bool = Field(sa_column=Column("DiscordManager", BOOLEAN), default=False)
|
||||
clan_leader: bool = Field(sa_column=Column("ClanLeader", BOOLEAN), default=False)
|
||||
discord_manager_speech_id: int | None = Field(
|
||||
sa_column=Column("DiscordManagerSpeechId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
clan_leader_speech_id: int | None = Field(
|
||||
sa_column=Column("ClanLeaderSpeechId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
|
||||
election: ElectionInformation | None = Relationship(back_populates="council_members")
|
||||
votes: list["Vote"] = Relationship(back_populates="council_member")
|
||||
|
||||
|
||||
class Vote(SQLModel, table=True):
|
||||
"""
|
||||
Represents a vote cast during an election.
|
||||
|
||||
Attributes:
|
||||
election_name: Election this vote was cast in.
|
||||
voter_id: User id that cast the vote.
|
||||
voted_for_id: User id that was voted for.
|
||||
type: Type of election, council, discordmanager, or clanleader.
|
||||
election: A link to the ElectionInformation object that the vote was cast in.
|
||||
council_member: A link to the CouncilMember object that the vote is for.
|
||||
"""
|
||||
|
||||
__tablename__: str = "Votes"
|
||||
|
||||
election_name: str = Field(
|
||||
sa_column=Column(
|
||||
"ElectionName",
|
||||
VARCHAR,
|
||||
ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
max_length=30,
|
||||
)
|
||||
voter_id: int = Field(sa_column=Column("VoterId", BIGINT(unsigned=True), primary_key=True, nullable=False))
|
||||
# might have to set this foreign key later with alembic migration...
|
||||
voted_for_id: int | None = Field(
|
||||
sa_column=Column("VotedForId", BIGINT(unsigned=True), ForeignKey("CouncilMembers.MemberId"), nullable=True)
|
||||
)
|
||||
type: str = Field(
|
||||
sa_column=Column("Type", VARCHAR, primary_key=True, nullable=False, default="council"), max_length=14
|
||||
)
|
||||
|
||||
election: ElectionInformation | None = Relationship(back_populates="votes")
|
||||
council_member: CouncilMember | None = Relationship(back_populates="votes")
|
||||
|
||||
|
||||
class UserBirthday(SQLModel, table=True):
|
||||
"""
|
||||
Represents a user's birth month and day.
|
||||
|
||||
Attributes:
|
||||
id: User's discord id.
|
||||
birthday: Month/day that is a user's birthday.
|
||||
"""
|
||||
|
||||
id: int = Field(sa_column=Column("UserId", BIGINT(unsigned=True), primary_key=True, nullable=False))
|
||||
|
||||
# TODO(imcompiling): split into month and day. Also relies on Alembic migration.
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/93
|
||||
birthday: str = Field(sa_column=Column("Birthday", VARCHAR, nullable=False), max_length=5)
|
||||
|
||||
|
||||
class Presence(SQLModel, table=True):
|
||||
"""
|
||||
Stores presences for the bot to pull and use.
|
||||
|
||||
Attributes:
|
||||
id: Auto-incremented id for each entry.
|
||||
presence: The presence itself.
|
||||
"""
|
||||
|
||||
__tablename__: str = "Presences"
|
||||
|
||||
id: int | None = Field(sa_column=Column("Id", INTEGER, primary_key=True), default=None)
|
||||
presence: str = Field(sa_column=Column("Presence", VARCHAR, nullable=False), max_length=100)
|
||||
|
||||
|
||||
class Points(SQLModel, table=True):
|
||||
"""
|
||||
Stores activity points a user can gain by participating in clan activities.
|
||||
|
||||
Event points and clan points were separated out.
|
||||
|
||||
Attributes:
|
||||
quarter_name: Name of the quarter the points were earned in.
|
||||
user_id: The user that earned them. Is a discord id if the user is a discord member, otherwise their rsn.
|
||||
discord_member: Boolean on whether the user is a discord member or not. Used to know what the user_id
|
||||
represents.
|
||||
event_points: Event points earned during the quarter.
|
||||
clan_points: Clan points earned during the quarter.
|
||||
"""
|
||||
|
||||
quarter_name: str = Field(sa_column=Column("QuarterName", VARCHAR, primary_key=True, nullable=False), max_length=30)
|
||||
user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32)
|
||||
discord_member: bool = Field(sa_column=Column("DiscordMember", BOOLEAN, nullable=False))
|
||||
event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0)
|
||||
clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0)
|
||||
|
||||
|
||||
class Message(SQLModel, table=True):
|
||||
"""
|
||||
Table is used to map folders and files to channels and messages in discord.
|
||||
|
||||
If folder name is stored in path, the corresponding id is a channel id. If the path is to a file, the id is for a
|
||||
message.
|
||||
|
||||
Attributes:
|
||||
path: Either directory or file pathname.
|
||||
id: Channel or message id depending on the path.
|
||||
"""
|
||||
|
||||
__tablename__: str = "Messages"
|
||||
|
||||
path: str = Field(sa_column=Column("Path", VARCHAR, primary_key=True, nullable=False), max_length=100)
|
||||
id: int = Field(sa_column=Column("Id", BIGINT(unsigned=True), nullable=False))
|
||||
|
||||
|
||||
class PointsLimit(SQLModel, table=True):
|
||||
"""
|
||||
Table tracks how many points a user has gained in a week.
|
||||
|
||||
Council didn't want users spamming points, so they added a limit to how many can be gained in a week. This table
|
||||
tracks point gain per week.
|
||||
|
||||
Attributes:
|
||||
user_id: The user that earned points. Is a discord id if the user is a discord member, otherwise their rsn.
|
||||
week: week number/year representation of the week. Ex: 04/2025.
|
||||
event_points: Number of event points earned during this week.
|
||||
clan_points: Number of clan points earned during this week.
|
||||
"""
|
||||
|
||||
user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32)
|
||||
|
||||
# Maybe should break this out into two columns, week and year. Better data validation and more clear.
|
||||
week: str = Field(sa_column=Column("Week", VARCHAR, primary_key=True, nullable=False), max_length=7)
|
||||
event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0)
|
||||
clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0)
|
||||
@@ -1,7 +0,0 @@
|
||||
[tool.ruff]
|
||||
# Extend the `pyproject.toml` file in the parent directory...
|
||||
extend = "../pyproject.toml"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# ignore implicit-namespace-package in this directory.
|
||||
ignore = ["INP001"]
|
||||
@@ -1,20 +0,0 @@
|
||||
"""Simple file to set up the sqlalchemy connection to database."""
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
|
||||
# connect with sql alchemy and autoload schema
|
||||
URL = "sqlite+pysqlite:///database/dakiny.db"
|
||||
engine = create_engine(URL)
|
||||
Base = automap_base()
|
||||
Base.prepare(autoload_with=engine)
|
||||
|
||||
# define the classes
|
||||
Points = Base.classes.Points
|
||||
PointsLimit = Base.classes.PointsLimit
|
||||
CouncilMembers = Base.classes.CouncilMembers
|
||||
ElectionInformation = Base.classes.ElectionInformation
|
||||
Messages = Base.classes.Messages
|
||||
Presences = Base.classes.Presences
|
||||
UserBirthday = Base.classes.UserBirthday
|
||||
Votes = Base.classes.Votes
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Creates logger for Dakiny bot."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
dakiny_logger = logging.getLogger("default")
|
||||
dakiny_logger.setLevel(logging.DEBUG if os.getenv("DEBUG") == "true" else logging.INFO)
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s:%(levelname)s [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
|
||||
)
|
||||
dakiny_logger.addHandler(handler)
|
||||
+107
-80
@@ -1,7 +1,7 @@
|
||||
"""File contains util functions."""
|
||||
|
||||
import calendar
|
||||
import os
|
||||
import pathlib
|
||||
from collections.abc import Sequence
|
||||
from datetime import UTC, datetime
|
||||
from enum import StrEnum
|
||||
@@ -9,19 +9,15 @@ from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
import nextcord
|
||||
from sqlalchemy import select, true, update
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.database.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
Messages,
|
||||
Points,
|
||||
PointsLimit,
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember, Message, Points, PointsLimit, Vote
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
|
||||
from .DakinySettings import settings
|
||||
from .dakiny_settings import settings
|
||||
|
||||
NOMINEE_SPLITTER = "========================================\n\n"
|
||||
REPO_DIR = "./dakiny-messages"
|
||||
@@ -34,20 +30,22 @@ VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
||||
PointType = StrEnum("PointType", ["CLAN", "EVENT"])
|
||||
|
||||
|
||||
def get_datetime(day: str) -> datetime:
|
||||
# TODO(imcompiling): resolve returning None from this function
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
def get_datetime(month_day: str) -> datetime: # pyright: ignore[reportReturnType]
|
||||
"""
|
||||
Takes in a string month and day and returns a datetime object of that Month/Day this year.
|
||||
|
||||
Args:
|
||||
day (str): Month/Day in that format.
|
||||
month_day (str): Month/Day in that format.
|
||||
|
||||
Returns:
|
||||
datetime: Datetime representation of Month/Day.
|
||||
"""
|
||||
today = datetime.now(tz=UTC).date()
|
||||
year = today.year
|
||||
month = int(day.split("/")[0])
|
||||
day = int(day.split("/")[1])
|
||||
month = int(month_day.split("/")[0])
|
||||
day = int(month_day.split("/")[1])
|
||||
|
||||
# ensure that year rolls over if current month is December and the next month is less than 12.
|
||||
if today.month == calendar.DECEMBER and month < calendar.OCTOBER:
|
||||
@@ -55,8 +53,10 @@ def get_datetime(day: str) -> datetime:
|
||||
|
||||
if today.month == calendar.JANUARY and month > calendar.MARCH:
|
||||
year -= 1
|
||||
|
||||
try:
|
||||
return datetime(year, month, day, tzinfo=UTC)
|
||||
except ValueError:
|
||||
dakiny_logger.warning("Invalid month day: %s.", month_day)
|
||||
|
||||
|
||||
def get_election_name() -> str:
|
||||
@@ -92,12 +92,14 @@ def get_active_election_cycle() -> int:
|
||||
int: cycle index for active election. None for no active cycle
|
||||
"""
|
||||
today = datetime.now(tz=UTC)
|
||||
dakiny_logger.debug("Today is %s", today)
|
||||
|
||||
for cycle, election in enumerate(settings.election_cycles):
|
||||
if (
|
||||
today.date() >= get_datetime(election.election_start).date()
|
||||
and today.date() <= get_datetime(election.term_start).date()
|
||||
):
|
||||
dakiny_logger.debug("Cycle is %d", cycle)
|
||||
return cycle
|
||||
|
||||
# It isn't a current election, get the quarter
|
||||
@@ -114,14 +116,19 @@ def get_quarter_cycle() -> int:
|
||||
int: cycle index for current cycle.
|
||||
"""
|
||||
today = datetime.now(tz=UTC)
|
||||
dakiny_logger.debug("Today is %s", today)
|
||||
for cycle, election in enumerate(settings.election_cycles):
|
||||
if (
|
||||
today.date() >= get_datetime(election.term_start).date()
|
||||
and today.date() <= get_datetime(election.term_end).date()
|
||||
):
|
||||
dakiny_logger.debug("Cycle is %d", cycle)
|
||||
return cycle
|
||||
|
||||
return None
|
||||
dakiny_logger.debug("Quarter cycle is None.")
|
||||
# TODO(imcompiling) Add None to return signature or find better return value
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
return None # pyright: ignore[reportReturnType]
|
||||
|
||||
|
||||
def build_votes_str(guild: nextcord.Guild) -> str:
|
||||
@@ -139,19 +146,20 @@ def build_votes_str(guild: nextcord.Guild) -> str:
|
||||
election_name = get_election_name()
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(CouncilMembers).where(CouncilMembers.ElectionName == election_name)
|
||||
stmt = select(CouncilMember).where(col(CouncilMember.election_name == election_name))
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
):
|
||||
votes[nominee.MemberId] = 0
|
||||
votes[nominee.member_id] = 0
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.COUNCIL)
|
||||
stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.COUNCIL))
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.VotedForId is not None:
|
||||
votes[vote.VotedForId] += 1
|
||||
if vote.voted_for_id is not None:
|
||||
votes[vote.voted_for_id] += 1
|
||||
|
||||
votes_str: str = "===================="
|
||||
votes_str += user_vote_str(votes)
|
||||
@@ -176,46 +184,52 @@ def build_highcouncil_votes_str(guild: nextcord.Guild) -> str:
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.clan_leader_speech_id is not None))
|
||||
)
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
):
|
||||
cl_votes[nominee.MemberId] = 0
|
||||
cl_votes[nominee.member_id] = 0
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.CLANLEADER)
|
||||
stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.CLANLEADER))
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.VotedForId is not None:
|
||||
cl_votes[vote.VotedForId] += 1
|
||||
if vote.voted_for_id is not None:
|
||||
cl_votes[vote.voted_for_id] += 1
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.discord_manager_speech_id is not None))
|
||||
)
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
):
|
||||
dm_votes[nominee.MemberId] = 0
|
||||
dm_votes[nominee.member_id] = 0
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
stmt = (
|
||||
select(Vote)
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.type == VoteType.DISCORDMANAGER))
|
||||
)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.VotedForId is not None:
|
||||
dm_votes[vote.VotedForId] += 1
|
||||
if vote.voted_for_id is not None:
|
||||
dm_votes[vote.voted_for_id] += 1
|
||||
|
||||
# build the votes string
|
||||
votes_str: str = "Clan Leader Votes\n===================="
|
||||
votes_str: str = "Clan Leader Vote\n===================="
|
||||
votes_str += user_vote_str(cl_votes)
|
||||
|
||||
votes_str += "\n\nDiscord Manager Votes\n===================="
|
||||
votes_str += "\n\nDiscord Manager Vote\n===================="
|
||||
votes_str += user_vote_str(dm_votes)
|
||||
|
||||
return votes_str
|
||||
@@ -309,7 +323,7 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
|
||||
quarter_name = get_quarter_name()
|
||||
with Session(engine) as session:
|
||||
for user in users:
|
||||
stmt = select(Points).where(Points.UserId == user).where(Points.QuarterName == quarter_name)
|
||||
stmt = select(Points).where(col(Points.user_id == user)).where(col(Points.quarter_name == quarter_name))
|
||||
|
||||
points_obj = session.scalar(stmt)
|
||||
add_points = get_allowed_points(session, point_type, user, add_points)
|
||||
@@ -317,16 +331,16 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
|
||||
add_event_points = add_points if point_type == PointType.EVENT else 0
|
||||
if points_obj is None:
|
||||
new_points = Points(
|
||||
QuarterName=quarter_name,
|
||||
UserId=user,
|
||||
DiscordMember=is_member,
|
||||
EventPoints=add_event_points,
|
||||
ClanPoints=add_clan_points,
|
||||
quarter_name=quarter_name,
|
||||
user_id=user,
|
||||
discord_member=is_member,
|
||||
event_points=add_event_points,
|
||||
clan_points=add_clan_points,
|
||||
)
|
||||
session.add(new_points)
|
||||
else:
|
||||
points_obj.EventPoints += add_event_points
|
||||
points_obj.ClanPoints += add_clan_points
|
||||
points_obj.event_points += add_event_points
|
||||
points_obj.clan_points += add_clan_points
|
||||
|
||||
session.commit()
|
||||
|
||||
@@ -356,7 +370,7 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
|
||||
if point_type == PointType.EVENT
|
||||
else settings.point_settings.clan_points_weekly_limit
|
||||
)
|
||||
stmt = select(PointsLimit).where(PointsLimit.UserId == user).where(PointsLimit.Week == this_week)
|
||||
stmt = select(PointsLimit).where(col(PointsLimit.user_id == user)).where(col(PointsLimit.week == this_week))
|
||||
|
||||
# if under limit, can add up to however many points would make the current limit equal to the
|
||||
# allowed limit.
|
||||
@@ -364,16 +378,16 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
|
||||
if limit_obj is None:
|
||||
clan_points = points if point_type == PointType.CLAN else 0
|
||||
event_points = points if point_type == PointType.EVENT else 0
|
||||
new_limit = PointsLimit(UserId=user, Week=this_week, EventPoints=event_points, ClanPoints=clan_points)
|
||||
new_limit = PointsLimit(user_id=user, week=this_week, event_points=event_points, clan_points=clan_points)
|
||||
add_points = points
|
||||
session.add(new_limit)
|
||||
else:
|
||||
curr_points = limit_obj.EventPoints if point_type == PointType.EVENT else limit_obj.ClanPoints
|
||||
curr_points = limit_obj.event_points if point_type == PointType.EVENT else limit_obj.clan_points
|
||||
add_points = points if curr_points + points <= points_limit else (max(0, points_limit - curr_points))
|
||||
if point_type == PointType.EVENT:
|
||||
limit_obj.EventPoints += add_points
|
||||
limit_obj.event_points += add_points
|
||||
else:
|
||||
limit_obj.ClanPoints += add_points
|
||||
limit_obj.clan_points += add_points
|
||||
|
||||
session.commit()
|
||||
|
||||
@@ -402,8 +416,8 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[
|
||||
dir_path = f"{MESSAGES_DIR}/{dir_name}"
|
||||
channel = await get_clan_message_channel(session, dir_name, guild)
|
||||
sorted_files = sorted(
|
||||
os.listdir(dir_path),
|
||||
key=lambda filename: int(filename.split(".")[0]),
|
||||
pathlib.Path(dir_path).iterdir(),
|
||||
key=lambda file: int(file.name.split(".")[0]),
|
||||
)
|
||||
|
||||
for file_name in sorted_files:
|
||||
@@ -432,40 +446,48 @@ async def get_clan_message_channel(session: Session, channel_name: str, guild: n
|
||||
Returns:
|
||||
nextcord.TextChannel: Channel object with name channel_name.
|
||||
"""
|
||||
stmt = select(Messages.Id).where(Messages.Path == channel_name)
|
||||
stmt = select(col(Message.id)).where(col(Message.path == channel_name))
|
||||
channel_id = session.scalar(stmt)
|
||||
if channel_id is None:
|
||||
ret_channel = None
|
||||
if channel_id is None:
|
||||
# channel is not tracked. see if it exists
|
||||
channels = await guild.fetch_channels()
|
||||
for channel in channels:
|
||||
if channel.name == channel_name and channel.__class__ is nextcord.TextChannel:
|
||||
if channel.name == channel_name and isinstance(channel, nextcord.TextChannel):
|
||||
ret_channel = channel
|
||||
|
||||
if ret_channel is None:
|
||||
# channel doesn't exist, we must create it
|
||||
ret_channel = await guild.create_text_channel(
|
||||
channel_name,
|
||||
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||
reason="Creating channel as required for by design of Dakiny-Message repo.",
|
||||
)
|
||||
|
||||
# record channel information
|
||||
new_item = Messages(Path=channel_name, Id=ret_channel.id)
|
||||
new_item = Message(path=channel_name, id=ret_channel.id)
|
||||
session.add(new_item)
|
||||
session.commit()
|
||||
return ret_channel
|
||||
|
||||
else:
|
||||
try:
|
||||
return await guild.fetch_channel(channel_id)
|
||||
ret_channel = await guild.fetch_channel(channel_id)
|
||||
if not isinstance(ret_channel, nextcord.TextChannel):
|
||||
dakiny_logger.warning(
|
||||
"Channel %s is not a text channel. Does not work for clan messages.", ret_channel.name
|
||||
)
|
||||
# TODO(imcompiling) Figure out how to handle returning None here. Maybe delete channel and remake it?
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
return None # pyright: ignore[reportReturnType]
|
||||
except nextcord.NotFound:
|
||||
# the channel was deleted for some reason
|
||||
ret_channel = await guild.create_text_channel(
|
||||
channel_name,
|
||||
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||
reason="Creating channel as required for by design of Dakiny-Message repo.",
|
||||
)
|
||||
stmt = update(Messages).where(Messages.Path == channel_name).values(Id=ret_channel.id)
|
||||
stmt = update(Message).where(col(Message.path == channel_name)).values(id=ret_channel.id)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
return ret_channel
|
||||
|
||||
|
||||
@@ -488,21 +510,21 @@ async def edit_clan_message(
|
||||
message_name (str): Name of the message. Corresponds to the file that contains message content.
|
||||
message_content (str): Content of the message to edit/send.
|
||||
"""
|
||||
stmt = select(Messages.Id).where(Messages.Path == message_name)
|
||||
stmt = select(col(Message.id)).where(col(Message.path == message_name))
|
||||
msg_id = session.scalar(stmt)
|
||||
if msg_id is None:
|
||||
# don't have a message for this file
|
||||
msg = await channel.send(message_content)
|
||||
new_message = Messages(Path=message_name, Id=msg.id)
|
||||
new_message = Message(path=message_name, id=msg.id)
|
||||
session.add(new_message)
|
||||
session.commit()
|
||||
else:
|
||||
try:
|
||||
msg: nextcord.Message = await channel.fetch_message(msg_id)
|
||||
await msg.edit(message_content)
|
||||
await msg.edit(content=message_content)
|
||||
except nextcord.NotFound:
|
||||
msg = await channel.send(message_content)
|
||||
stmt = update(Messages).where(Messages.Path == message_name).values(Id=msg.id)
|
||||
stmt = update(Message).where(col(Message.path == message_name)).values(id=msg.id)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
@@ -514,30 +536,35 @@ async def edit_clan_council_info(guild: nextcord.Guild) -> None:
|
||||
Args:
|
||||
guild (nextcord.Guild): Guild object that to use to get channel.
|
||||
"""
|
||||
clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(settings.channel_ids.clan_info)
|
||||
clan_info_channel = await guild.fetch_channel(settings.channel_ids.clan_info)
|
||||
|
||||
if not isinstance(clan_info_channel, nextcord.TextChannel):
|
||||
return
|
||||
|
||||
election_name = get_election_name()
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.DiscordManager == true())
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.discord_manager is True))
|
||||
)
|
||||
discord_manager = session.scalar(stmt)
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.ClanLeader == true())
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.clan_leader is True))
|
||||
)
|
||||
clan_leader = session.scalar(stmt)
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.Elected == true())
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.elected is True))
|
||||
)
|
||||
|
||||
council_member_ids = sorted(
|
||||
session.scalars(stmt), key=lambda user_id: guild.get_member(user_id).display_name.lower()
|
||||
session.scalars(stmt),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda user_id: guild.get_member(user_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
)
|
||||
|
||||
council_members_strs = [
|
||||
|
||||
+5
-3
@@ -1,17 +1,19 @@
|
||||
[project]
|
||||
authors = [{ name = "imcompiling", email = "[email protected]" }]
|
||||
requires-python = "<4.0,>=3.12"
|
||||
requires-python = "<4.0,>=3.13"
|
||||
dependencies = [
|
||||
"gitpython<4.0.0,>=3.1.43",
|
||||
"fastapi<1.0.0,>=0.111.0",
|
||||
"nextcord<3.0.0,>=2.6.0",
|
||||
"pandas<3.0.0,>=2.2.2",
|
||||
"pytz<2025.0,>=2024.1",
|
||||
"pytz<2025.3,>=2025.2",
|
||||
"sqlalchemy<3.0.0,>=2.0.30",
|
||||
"aiofiles<24.0.0,>=23.2.1",
|
||||
"setuptools<71.0.0,>=70.0.0",
|
||||
"setuptools<80.9.1,>=80.9.0",
|
||||
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
||||
"uvicorn>=0.34.0",
|
||||
"audioop-lts>=0.2.1",
|
||||
"sqlmodel>=0.0.24",
|
||||
]
|
||||
name = "dakiny-bot"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"lockFileMaintenance": {
|
||||
"automerge": true
|
||||
},
|
||||
"major": {
|
||||
"automerge": false,
|
||||
"automergeType": "pr"
|
||||
},
|
||||
"minor": {
|
||||
"automerge": true,
|
||||
"automergeType": "branch"
|
||||
},
|
||||
"patch": {
|
||||
"automerge": true,
|
||||
"automergeType": "branch"
|
||||
},
|
||||
"pin": {
|
||||
"automerge": true,
|
||||
"automergeType": "branch"
|
||||
},
|
||||
"ignoreTests": true,
|
||||
"dockerfile": {
|
||||
"minor": {
|
||||
"automerge": false,
|
||||
"automergeType": "pr"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
requires-python = ">=3.12, <4.0"
|
||||
requires-python = ">=3.13, <4.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
@@ -34,21 +34,6 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/ed/f26db39d29cd3cb2f5a3374304c713fe5ab5a0e4c8ee25a0c45cc6adf844/aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e", size = 7669618 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/cf/4bda538c502f9738d6b95ada11603c05ec260807246e15e869fc3ec5de97/aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886", size = 704666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/7b/87fcef2cad2fad420ca77bef981e815df6904047d0a1bd6aeded1b0d1d66/aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2", size = 464057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/a6/789e1f17a1b6f4a38939fbc39d29e1d960d5f89f73d0629a939410171bc0/aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c", size = 455996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/dd/485061fbfef33165ce7320db36e530cd7116ee1098e9c3774d15a732b3fd/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a", size = 1682367 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/d7/9ec5b3ea9ae215c311d88b2093e8da17e67b8856673e4166c994e117ee3e/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231", size = 1736989 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/fb/ea94927f7bfe1d86178c9d3e0a8c54f651a0a655214cce930b3c679b8f64/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e", size = 1793265 },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/7f/6de218084f9b653026bd7063cd8045123a7ba90c25176465f266976d8c82/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8", size = 1691841 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/e2/992f43d87831cbddb6b09c57ab55499332f60ad6fdbf438ff4419c2925fc/aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8", size = 1619317 },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/74/879b23cdd816db4133325a201287c95bef4ce669acde37f8f1b8669e1755/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c", size = 1641416 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/98/b123f6b15d87c54e58fd7ae3558ff594f898d7f30a90899718f3215ad328/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab", size = 1646514 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/38/257fda3dc99d6978ab943141d5165ec74fd4b4164baa15e9c66fa21da86b/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da", size = 1702095 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/f4/ddab089053f9fb96654df5505c0a69bde093214b3c3454f6bfdb1845f558/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853", size = 1734611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/d6/f30b2bc520c38c8aa4657ed953186e535ae84abe55c08d0f70acd72ff577/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e", size = 1694576 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/97/b0a88c3f4c6d0020b34045ee6d954058abc870814f6e310c4c9b74254116/aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600", size = 411363 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/23/cc36d9c398980acaeeb443100f0216f50a7cfe20c67a9fd0a2f1a5a846de/aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d", size = 437666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d1/d8af164f400bad432b63e1ac857d74a09311a8334b0481f2f64b158b50eb/aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9", size = 697982 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/d1/faad3bf9fa4bfd26b95c69fc2e98937d52b1ff44f7e28131855a98d23a17/aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194", size = 460662 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/61/0d71cc66d63909dabc4590f74eba71f91873a77ea52424401c2498d47536/aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f", size = 452950 },
|
||||
@@ -94,7 +79,6 @@ source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 }
|
||||
wheels = [
|
||||
@@ -110,6 +94,46 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "audioop-lts"
|
||||
version = "0.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/3b/69ff8a885e4c1c42014c2765275c4bd91fe7bc9847e9d8543dbcbb09f820/audioop_lts-0.2.1.tar.gz", hash = "sha256:e81268da0baa880431b68b1308ab7257eb33f356e57a5f9b1f915dfb13dd1387", size = 30204 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/91/a219253cc6e92db2ebeaf5cf8197f71d995df6f6b16091d1f3ce62cb169d/audioop_lts-0.2.1-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd1345ae99e17e6910f47ce7d52673c6a1a70820d78b67de1b7abb3af29c426a", size = 46252 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/f6/3cb21e0accd9e112d27cee3b1477cd04dafe88675c54ad8b0d56226c1e0b/audioop_lts-0.2.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:e175350da05d2087e12cea8e72a70a1a8b14a17e92ed2022952a4419689ede5e", size = 27183 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/7e/f94c8a6a8b2571694375b4cf94d3e5e0f529e8e6ba280fad4d8c70621f27/audioop_lts-0.2.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:4a8dd6a81770f6ecf019c4b6d659e000dc26571b273953cef7cd1d5ce2ff3ae6", size = 26726 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/f8/a0e8e7a033b03fae2b16bc5aa48100b461c4f3a8a38af56d5ad579924a3a/audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cd3c0b6f2ca25c7d2b1c3adeecbe23e65689839ba73331ebc7d893fcda7ffe", size = 80718 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/ea/a98ebd4ed631c93b8b8f2368862cd8084d75c77a697248c24437c36a6f7e/audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff3f97b3372c97782e9c6d3d7fdbe83bce8f70de719605bd7ee1839cd1ab360a", size = 88326 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/79/e97a9f9daac0982aa92db1199339bd393594d9a4196ad95ae088635a105f/audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a351af79edefc2a1bd2234bfd8b339935f389209943043913a919df4b0f13300", size = 80539 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/d3/1051d80e6f2d6f4773f90c07e73743a1e19fcd31af58ff4e8ef0375d3a80/audioop_lts-0.2.1-cp313-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aeb6f96f7f6da80354330470b9134d81b4cf544cdd1c549f2f45fe964d28059", size = 78577 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1d/54f4c58bae8dc8c64a75071c7e98e105ddaca35449376fcb0180f6e3c9df/audioop_lts-0.2.1-cp313-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c589f06407e8340e81962575fcffbba1e92671879a221186c3d4662de9fe804e", size = 82074 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/89/2e78daa7cebbea57e72c0e1927413be4db675548a537cfba6a19040d52fa/audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fbae5d6925d7c26e712f0beda5ed69ebb40e14212c185d129b8dfbfcc335eb48", size = 84210 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/57/3ff8a74df2ec2fa6d2ae06ac86e4a27d6412dbb7d0e0d41024222744c7e0/audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_i686.whl", hash = "sha256:d2d5434717f33117f29b5691fbdf142d36573d751716249a288fbb96ba26a281", size = 85664 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/01/21cc4e5878f6edbc8e54be4c108d7cb9cb6202313cfe98e4ece6064580dd/audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f626a01c0a186b08f7ff61431c01c055961ee28769591efa8800beadd27a2959", size = 93255 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/28/7f7418c362a899ac3b0bf13b1fde2d4ffccfdeb6a859abd26f2d142a1d58/audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:05da64e73837f88ee5c6217d732d2584cf638003ac72df124740460531e95e47", size = 87760 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/d8/577a8be87dc7dd2ba568895045cee7d32e81d85a7e44a29000fe02c4d9d4/audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:56b7a0a4dba8e353436f31a932f3045d108a67b5943b30f85a5563f4d8488d77", size = 84992 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/9a/4699b0c4fcf89936d2bfb5425f55f1a8b86dff4237cfcc104946c9cd9858/audioop_lts-0.2.1-cp313-abi3-win32.whl", hash = "sha256:6e899eb8874dc2413b11926b5fb3857ec0ab55222840e38016a6ba2ea9b7d5e3", size = 26059 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/1c/1f88e9c5dd4785a547ce5fd1eb83fff832c00cc0e15c04c1119b02582d06/audioop_lts-0.2.1-cp313-abi3-win_amd64.whl", hash = "sha256:64562c5c771fb0a8b6262829b9b4f37a7b886c01b4d3ecdbae1d629717db08b4", size = 30412 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/e9/c123fd29d89a6402ad261516f848437472ccc602abb59bba522af45e281b/audioop_lts-0.2.1-cp313-abi3-win_arm64.whl", hash = "sha256:c45317debeb64002e980077642afbd977773a25fa3dfd7ed0c84dccfc1fafcb0", size = 23578 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/99/bb664a99561fd4266687e5cb8965e6ec31ba4ff7002c3fce3dc5ef2709db/audioop_lts-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3827e3fce6fee4d69d96a3d00cd2ab07f3c0d844cb1e44e26f719b34a5b15455", size = 46827 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/e3/f664171e867e0768ab982715e744430cf323f1282eb2e11ebfb6ee4c4551/audioop_lts-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:161249db9343b3c9780ca92c0be0d1ccbfecdbccac6844f3d0d44b9c4a00a17f", size = 27479 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/0d/2a79231ff54eb20e83b47e7610462ad6a2bea4e113fae5aa91c6547e7764/audioop_lts-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b7b4ff9de7a44e0ad2618afdc2ac920b91f4a6d3509520ee65339d4acde5abf", size = 27056 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/46/342471398283bb0634f5a6df947806a423ba74b2e29e250c7ec0e3720e4f/audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72e37f416adb43b0ced93419de0122b42753ee74e87070777b53c5d2241e7fab", size = 87802 },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/44/7a85b08d4ed55517634ff19ddfbd0af05bf8bfd39a204e4445cd0e6f0cc9/audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534ce808e6bab6adb65548723c8cbe189a3379245db89b9d555c4210b4aaa9b6", size = 95016 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/2a/45edbca97ea9ee9e6bbbdb8d25613a36e16a4d1e14ae01557392f15cc8d3/audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2de9b6fb8b1cf9f03990b299a9112bfdf8b86b6987003ca9e8a6c4f56d39543", size = 87394 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/ae/832bcbbef2c510629593bf46739374174606e25ac7d106b08d396b74c964/audioop_lts-0.2.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f24865991b5ed4b038add5edbf424639d1358144f4e2a3e7a84bc6ba23e35074", size = 84874 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/1c/8023c3490798ed2f90dfe58ec3b26d7520a243ae9c0fc751ed3c9d8dbb69/audioop_lts-0.2.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bdb3b7912ccd57ea53197943f1bbc67262dcf29802c4a6df79ec1c715d45a78", size = 88698 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/db/5379d953d4918278b1f04a5a64b2c112bd7aae8f81021009da0dcb77173c/audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:120678b208cca1158f0a12d667af592e067f7a50df9adc4dc8f6ad8d065a93fb", size = 90401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/6e/3c45d316705ab1aec2e69543a5b5e458d0d112a93d08994347fafef03d50/audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:54cd4520fc830b23c7d223693ed3e1b4d464997dd3abc7c15dce9a1f9bd76ab2", size = 91864 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/58/6a371d8fed4f34debdb532c0b00942a84ebf3e7ad368e5edc26931d0e251/audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:d6bd20c7a10abcb0fb3d8aaa7508c0bf3d40dfad7515c572014da4b979d3310a", size = 98796 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/77/d637aa35497e0034ff846fd3330d1db26bc6fd9dd79c406e1341188b06a2/audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f0ed1ad9bd862539ea875fb339ecb18fcc4148f8d9908f4502df28f94d23491a", size = 94116 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/60/7afc2abf46bbcf525a6ebc0305d85ab08dc2d1e2da72c48dbb35eee5b62c/audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e1af3ff32b8c38a7d900382646e91f2fc515fd19dea37e9392275a5cbfdbff63", size = 91520 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/6d/42d40da100be1afb661fd77c2b1c0dfab08af1540df57533621aea3db52a/audioop_lts-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:f51bb55122a89f7a0817d7ac2319744b4640b5b446c4c3efcea5764ea99ae509", size = 26482 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/09/f08494dca79f65212f5b273aecc5a2f96691bf3307cac29acfcf84300c01/audioop_lts-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f0f2f336aa2aee2bce0b0dcc32bbba9178995454c7b979cf6ce086a8801e14c7", size = 30780 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/35/be73b6015511aa0173ec595fc579133b797ad532996f2998fd6b8d1bbe6b/audioop_lts-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:78bfb3703388c780edf900be66e07de5a3d4105ca8e8720c5c4d67927e0b15d0", size = 23918 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
@@ -137,6 +161,7 @@ version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aiofiles" },
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "dataclass-wizard", extra = ["yaml"] },
|
||||
{ name = "fastapi" },
|
||||
{ name = "gitpython" },
|
||||
@@ -145,6 +170,7 @@ dependencies = [
|
||||
{ name = "pytz" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "sqlmodel" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
@@ -156,14 +182,16 @@ dev = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiofiles", specifier = ">=23.2.1,<24.0.0" },
|
||||
{ name = "audioop-lts", specifier = ">=0.2.1" },
|
||||
{ name = "dataclass-wizard", extras = ["yaml"], specifier = ">=0.22.3,<1.0.0" },
|
||||
{ name = "fastapi", specifier = ">=0.111.0,<1.0.0" },
|
||||
{ name = "gitpython", specifier = ">=3.1.43,<4.0.0" },
|
||||
{ name = "nextcord", specifier = ">=2.6.0,<3.0.0" },
|
||||
{ name = "pandas", specifier = ">=2.2.2,<3.0.0" },
|
||||
{ name = "pytz", specifier = ">=2024.1,<2025.0" },
|
||||
{ name = "setuptools", specifier = ">=70.0.0,<71.0.0" },
|
||||
{ name = "pytz", specifier = ">=2025.2,<2025.3" },
|
||||
{ name = "setuptools", specifier = ">=80.9.0,<80.9.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.30,<3.0.0" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||
{ name = "uvicorn", specifier = ">=0.34.0" },
|
||||
]
|
||||
|
||||
@@ -174,9 +202,6 @@ dev = [{ name = "ruff", specifier = ">=0.6.9,<1.0.0" }]
|
||||
name = "dataclass-wizard"
|
||||
version = "0.33.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/2a/6ae6638cd5ca919b73d393cdb4a4e53c8c3dcacc003a79d9480ecad46798/dataclass-wizard-0.33.0.tar.gz", hash = "sha256:10edbac626c390ed1f3be4f972303bc2c25c162fd453d55adfe22b05b263ee6d", size = 269911 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e6/fa8b6615f988a271b1ab990f0086f90c936608762f02d02db9164cfd7ff9/dataclass_wizard-0.33.0-py2.py3-none-any.whl", hash = "sha256:d257d65a707f6ea76140ad5442189ffe6fc68e1d678dc6b3fa25754e3106160d", size = 164545 },
|
||||
@@ -207,21 +232,6 @@ version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 },
|
||||
@@ -264,39 +274,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
@@ -321,21 +298,6 @@ version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 },
|
||||
@@ -370,16 +332,6 @@ version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/fdbf6a7871703df6160b5cf3dd774074b086d278172285c52c2758b76305/numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918", size = 20227662 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/12/b928871c570d4a87ab13d2cc19f8817f17e340d5481621930e76b80ffb7d/numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab", size = 20909861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/c3/59df91ae1d8ad7c5e03efd63fd785dec62d96b0fe56d1f9ab600b55009af/numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa", size = 14095776 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/4e/8ed5868efc8e601fb69419644a280e9c482b75691466b73bfaab7d86922c/numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315", size = 5126239 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/74/dd0bbe650d7bc0014b051f092f2de65e34a8155aabb1287698919d124d7f/numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355", size = 6659296 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/11/4ebd7a3f4a655764dc98481f97bd0a662fb340d1001be6050606be13e162/numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7", size = 14047121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/a7/c1f1d978166eb6b98ad009503e4d93a8c1962d0eb14a885c352ee0276a54/numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d", size = 16096599 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/6d/0e22afd5fcbb4d8d0091f3f46bf4e8906399c458d4293da23292c0ba5022/numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51", size = 15243932 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/39/e4e5832820131ba424092b9610d996b37e5557180f8e2d6aebb05c31ae54/numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046", size = 17861032 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/8a/3794313acbf5e70df2d5c7d2aba8718676f8d054a05abe59e48417fb2981/numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2", size = 6274018 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/c1/c31d3637f2641e25c7a19adf2ae822fdaf4ddd198b05d79a92a9ce7cb63e/numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8", size = 12613843 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d6/91a26e671c396e0c10e327b763485ee295f5a5a7a48c553f18417e5a0ed5/numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780", size = 20896464 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/40/5792ccccd91d45e87d9e00033abc4f6ca8a828467b193f711139ff1f1cd9/numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821", size = 14111350 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/2a/fb0a27f846cb857cef0c4c92bef89f133a3a1abb4e16bba1c4dace2e9b49/numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e", size = 5111629 },
|
||||
@@ -414,13 +366,6 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
|
||||
@@ -442,22 +387,6 @@ version = "0.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 },
|
||||
@@ -500,20 +429,6 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
|
||||
@@ -544,11 +459,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.2"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -557,15 +472,6 @@ version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
|
||||
@@ -604,11 +510,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "70.3.0"
|
||||
version = "80.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/d8/10a70e86f6c28ae59f101a9de6d77bf70f147180fbf40c3af0f64080adc3/setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5", size = 2333112 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/15/88e46eb9387e905704b69849618e699dc2f54407d8953cc4ec4b8b46528d/setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc", size = 931070 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -643,19 +549,10 @@ name = "sqlalchemy"
|
||||
version = "2.0.36"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 },
|
||||
@@ -667,6 +564,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlmodel"
|
||||
version = "0.0.24"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "sqlalchemy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.41.3"
|
||||
@@ -721,22 +631,6 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 },
|
||||
|
||||
Reference in New Issue
Block a user