from sys import version as pyversion from discord import Guild from pydantic import BaseModel, ConfigDict from redbot import version_info from redbot.core.bot import commands from ..config import config from .provider import Provider class Repository(BaseModel): """Model for a repository configuration. Attributes: cog (commands.Cog): The cog object that allows accessing a discord.py Bot object, along other things. owner (str): The owner of the repository. repository (str): The name of the repository. prefix (str): Prefix used for determining which repository to retrieve an issue from when using the `#` syntax provider (Provider): The provider configuration for the repository. This requires a provider to be registered at the given URL. guild (Guild | None): The Discord guild associated with the repository. If None, the repository is considered a global repository. Properties: url (str): The URL for the repository. user_agent (str): The user agent for API requests using this object. Methods: to_config: Save the repository to Red's configuration. Saves to guilds if the `guild` class attribute is set. remove_config: Remove the repository from Red's configuration. Removes from guilds if the `guild` class attribute is set. Class Methods: from_config: Create a Repository instance from the Red configuration. Checks both global and guild repositories, prioritizing guild repositories. from_prefix: Create a Repository instance from the Red configuration using the prefix. Checks both global and guild repositories, prioritizing guild repositories. fetch_all: Fetch all repositories from the Red configuration. Returns a list of Repository instances. """ model_config = ConfigDict(arbitrary_types_allowed=True) cog: commands.Cog owner: str name: str prefix: str | None = None provider: Provider guild: Guild | None = None @property def url(self) -> str: """Return the URL for the repository.""" return f"{self.provider.url.rstrip('/')}/{self.owner}/{self.name}" @property def user_agent(self) -> str: """Return the user agent for API requests using this object.""" return f"Red-DiscordBot/{version_info} {self.cog.__cog_name__}/{self.cog.__version__} ({self.cog.__git__}) (Python {pyversion})" async def to_config(self) -> None: """Save the repository to Red's configuration. Saves to guilds if the `guild` class attribute is set.""" if self.guild: repositories = await config.guild(self.guild).repositories() else: repositories = await config.global_repositories() for repo in repositories: if repo["owner"] == self.owner and repo["name"] == self.name: repo["provider"] = self.provider.id if not any(repo["owner"] == self.owner and repo["name"] == self.name for repo in repositories): repositories.append({"owner": self.owner, "name": self.name, "prefix": self.prefix, "provider": self.provider.id}) if self.guild: await config.guild(self.guild).repositories.set(repositories) else: await config.global_repositories.set(repositories) async def remove_config(self) -> None: """Remove the repository from Red's configuration. Removes from guilds if the `guild` class attribute is set.""" if self.guild: repositories = await config.guild(self.guild).repositories() repositories = [repo for repo in repositories if not (repo["provider"] == self.provider.id and repo["owner"] == self.owner and repo["name"] == self.name)] await config.guild(self.guild).repositories.set(repositories) else: repositories = await config.global_repositories() repositories = [repo for repo in repositories if not (repo["provider"] == self.provider.id and repo["owner"] == self.owner and repo["name"] == self.name)] await config.global_repositories.set(repositories) @classmethod async def from_config(cls, cog: commands.Cog, provider_id: str, owner: str, repository: str, guild: Guild | None = None) -> "Repository": """Create a Repository instance from the Red configuration. Checks both global and guild repositories, prioritizing guild repositories.""" if guild: guild_repositories = await config.guild(guild).repositories() for repo in guild_repositories: if repo["provider"] == provider_id and repo["owner"] == owner and repo["name"] == repository: try: provider = await Provider.from_config(repo["provider"], guild=guild) except ValueError: pass return cls(cog=cog, guild=guild, owner=owner, name=repository, prefix=repo["prefix"], provider=provider) repositories = await config.global_repositories() for repo in repositories: if repo["provider"] == provider_id and repo["owner"] == owner and repo["name"] == repository: try: provider = await Provider.from_config(repo["provider"]) except ValueError as e: raise ValueError("Failed to create provider from config: '%s'" % str(e)) from e return cls(cog=cog, guild=None, owner=owner, name=repository, prefix=repo["prefix"], provider=provider) raise ValueError("No repository found for owner: '%s' and repository: '%s'" % (owner, repository)) @classmethod async def from_prefix(cls, cog: commands.Cog, prefix: str, guild: Guild | None = None) -> "Repository": """Create a Repository instance from the Red configuration using the prefix. Checks both global and guild repositories, prioritizing guild repositories.""" repositories = await cls.fetch_all(cog, guild) for repo in repositories: if repo.prefix is not None and repo.prefix == prefix: return repo raise ValueError("No repository found for prefix: '%s'" % prefix) @classmethod async def fetch_all(cls, cog: commands.Cog, guild: Guild | None = None) -> tuple["Repository"]: """Fetch all repositories from the Red configuration. Returns a list of Repository instances.""" repositories_list = [] if guild: guild_repositories = await config.guild(guild).repositories() for repo in guild_repositories: try: provider = await Provider.from_config(repo["provider"], guild=guild) except ValueError: continue repositories_list.append(cls(cog=cog, guild=guild, owner=repo["owner"], name=repo["name"], prefix=repo["prefix"], provider=provider)) global_repositories = await config.global_repositories() for repo in global_repositories: try: provider = await Provider.from_config(repo["provider"]) except ValueError: continue repositories_list.append(cls(cog=cog, guild=None, owner=repo["owner"], name=repo["name"], prefix=repo["prefix"], provider=provider)) return tuple(repositories_list)