Skip to content

Commit

Permalink
fix: use confz for env variable parsing (#119)
Browse files Browse the repository at this point in the history
* fix: use `confz` for env variable parsing

* ci: add missing env vars
  • Loading branch information
natelandau authored Jan 28, 2024
1 parent acabc0e commit 7e6988a
Show file tree
Hide file tree
Showing 18 changed files with 116 additions and 124 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Default configuration values. These are overwritten by docker-compose.yml environment variables
VALENTINA_LOG_FILE = "/valentina/valentina.log"
VALENTINA_LOG_LEVEL_AWS = "INFO" # Log level for AWS S3 client
VALENTINA_LOG_LEVEL_AWS = "ERROR" # Log level for AWS S3 client
VALENTINA_LOG_LEVEL_HTTP = "INFO" # Log level for discord HTTP, gateway, webhook, client events
VALENTINA_LOG_LEVEL = "INFO" # Overall log level for the bot
3 changes: 3 additions & 0 deletions .github/workflows/automated-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ env:
VALENTINA_TEST_MONGO_DATABASE_NAME: "valentina-test"
VALENTINA_MONGO_URI: ""
VALENTINA_MONGO_DATABASE_NAME: ""
VALENTINA_DISCORD_TOKEN: ""
VALENTINA_GUILDS: ""
VALENTINA_OWNER_CHANNELS: ""

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Before running Valentina, the following must be configured or installed.
| VALENTINA_LOG_FILE | `/valentina/valentina.log` | Sets the file to write logs to.<br />Note, this is the directory used within the Docker container |
| VALENTINA_LOG_LEVEL | `INFO` | Sets master log level. One of `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| VALENTINA_LOG_LEVEL_AWS | `INFO` | Sets the log level for AWS S3. One of `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| VALENTINA_LOG_LEVEL_HTTP | `INFO` | Sets the log level for discord HTTP, gateway, webhook,client events. One of `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| VALENTINA_LOG_LEVEL_HTTP | `WARNING` | Sets the log level for discord HTTP, gateway, webhook,client events. One of `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| VALENTINA_OWNER_CHANNELS | | Sets the Discord channels that are allowed to run bot admin commands. This is a comma separated string of Discord channel IDs. |
| VALENTINA_OWNER_IDS | | Sets the Discord user IDs that are allowed to run bot admin commands. This is a comma separated string of Discord user IDs. |
| VALENTINA_MONGO_URI | `mongodb://localhost:27017` | Production MongoDB URI |
Expand Down
19 changes: 18 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 15 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@
valentina = "valentina.main:app"

[tool.poetry.dependencies]
aiofiles = "^23.2.1"
arrow = "^1.3.0"
beanie = "^1.25.0"
boto3 = "^1.34.27"
inflect = "^7.0.0"
loguru = "^0.7.2"
numpy = "^1.26.3"
py-cord = "^2.4.1"
pydantic = "^2.5.3"
pygithub = "^2.1.1"
python = ">=3.11,<3.13"
python-dotenv = "^1.0.1"
rich = "^13.7.0"
semver = "^3.0.2"
typer = { extras = ["all"], version = "^0.9.0" }
aiofiles = "^23.2.1"
arrow = "^1.3.0"
beanie = "^1.25.0"
boto3 = "^1.34.27"
confz = "^2.0.1"
inflect = "^7.0.0"
loguru = "^0.7.2"
numpy = "^1.26.3"
py-cord = "^2.4.1"
pydantic = "^2.5.3"
pygithub = "^2.1.1"
python = ">=3.11,<3.13"
rich = "^13.7.0"
semver = "^3.0.2"
typer = { extras = ["all"], version = "^0.9.0" }

[tool.poetry.group.test.dependencies]
dirty-equals = "^0.7.1.post0"
Expand Down
7 changes: 3 additions & 4 deletions src/valentina/cogs/developer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@
User,
)
from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils import instantiate_logger
from valentina.utils import ValentinaConfig, instantiate_logger
from valentina.utils.autocomplete import (
select_aws_object_from_guild,
select_changelog_version_1,
select_changelog_version_2,
select_char_class,
)
from valentina.utils.converters import ValidCharClass
from valentina.utils.helpers import get_config_value
from valentina.views import confirm_action, present_embed

p = inflect.engine()
Expand Down Expand Up @@ -337,7 +336,7 @@ async def debug_send_log(
) -> None:
"""Send the bot's logs to the user."""
ctx.log_command("Send the bot's logs", LogLevel.DEBUG)
log_file = get_config_value("VALENTINA_LOG_FILE")
log_file = ValentinaConfig().log_file
file = discord.File(log_file)
await ctx.respond(file=file, ephemeral=hidden)

Expand All @@ -357,7 +356,7 @@ async def debug_tail_logs(
max_lines_from_bottom = 20
log_lines = []

logfile = get_config_value("VALENTINA_LOG_FILE")
logfile = ValentinaConfig().log_file
async with aiofiles.open(logfile, mode="r") as f:
async for line in f:
if "has connected to Gateway" not in line:
Expand Down
13 changes: 6 additions & 7 deletions src/valentina/cogs/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

from valentina.constants import GithubIssueLabels, LogLevel
from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils import errors
from valentina.utils.helpers import get_config_value
from valentina.utils import ValentinaConfig, errors
from valentina.views import present_embed


Expand All @@ -27,12 +26,12 @@ async def fetch_github_repo(self) -> Repository:
if self.repo:
return self.repo

try:
token = get_config_value("VALENTINA_GITHUB_TOKEN")
repo_name = get_config_value("VALENTINA_GITHUB_REPO")
except errors.MissingConfigurationError as e:
token = ValentinaConfig().github_token
repo_name = ValentinaConfig().github_repo

if not token or not repo_name:
msg = "Github"
raise errors.ServiceDisabledError(msg) from e
raise errors.ServiceDisabledError(msg)

try:
auth = Auth.Token(token)
Expand Down
4 changes: 2 additions & 2 deletions src/valentina/cogs/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from loguru import logger

from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils.helpers import get_config_value
from valentina.utils import ValentinaConfig
from valentina.views import auto_paginate, present_embed


Expand All @@ -26,7 +26,7 @@ def __build_command_list(self, ctx: ValentinaContext) -> list:
# build user specific list of commands to hide
hidden_commands = ["Owner"] # Always hide the "Owner" cog

owner_ids = get_config_value("VALENTINA_OWNER_IDS", pass_none=True)
owner_ids = ValentinaConfig().owner_ids
if owner_ids:
owners = [int(x) for x in owner_ids.split(",")]
if ctx.author.id not in owners:
Expand Down
9 changes: 4 additions & 5 deletions src/valentina/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
from loguru import logger

from valentina.models.bot import Valentina
from valentina.utils import instantiate_logger
from valentina.utils import ValentinaConfig, instantiate_logger
from valentina.utils.database import test_db_connection
from valentina.utils.helpers import get_config_value

from .__version__ import __version__

Expand Down Expand Up @@ -45,12 +44,12 @@ def main(
# Instantiate the bot
intents = discord.Intents.all()
bot = Valentina(
debug_guilds=[int(g) for g in get_config_value("VALENTINA_GUILDS", "").split(",")],
debug_guilds=[int(g) for g in ValentinaConfig().guilds.split(",")],
intents=intents,
owner_ids=[int(o) for o in get_config_value("VALENTINA_OWNER_IDS", "").split(",")],
owner_ids=[int(o) for o in ValentinaConfig().owner_ids.split(",")],
parent_dir=Path(__file__).parents[2].absolute(),
command_prefix="∑", # Effectively remove the command prefix by setting it to 'sigma'
version=__version__,
)

bot.run(get_config_value("VALENTINA_DISCORD_TOKEN")) # run the bot
bot.run(ValentinaConfig().discord_token) # run the bot
16 changes: 7 additions & 9 deletions src/valentina/models/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
from botocore.exceptions import ClientError
from loguru import logger

from valentina.utils import errors
from valentina.utils.helpers import get_config_value
from valentina.utils import ValentinaConfig, errors


class AWSService:
Expand All @@ -23,14 +22,13 @@ def __init__(self) -> None:
aws_secret_access_key (str): AWS secret access key.
bucket_name (str): Name of the S3 bucket to use.
"""
try:
self.aws_access_key_id = get_config_value("VALENTINA_AWS_ACCESS_KEY_ID")
self.aws_secret_access_key = get_config_value("VALENTINA_AWS_SECRET_ACCESS_KEY")
self.bucket_name = get_config_value("VALENTINA_S3_BUCKET_NAME")
except errors.MissingConfigurationError as e:
logger.error(f"Failed to initialize AWS Service: {e}")
self.aws_access_key_id = ValentinaConfig().aws_access_key_id
self.aws_secret_access_key = ValentinaConfig().aws_secret_access_key
self.bucket_name = ValentinaConfig().s3_bucket_name

if not self.aws_access_key_id or not self.aws_secret_access_key or not self.bucket_name:
msg = "AWS"
raise errors.ServiceDisabledError(msg) from e
raise errors.MissingConfigurationError(msg)

self.s3 = boto3.client(
"s3",
Expand Down
7 changes: 2 additions & 5 deletions src/valentina/models/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
Guild,
User,
)
from valentina.utils import errors
from valentina.utils import ValentinaConfig, errors
from valentina.utils.database import init_database
from valentina.utils.discord_utils import set_channel_perms
from valentina.utils.helpers import get_config_value


# Subclass discord.ApplicationContext to create custom application context
Expand Down Expand Up @@ -411,9 +410,7 @@ def __init__(self, parent_dir: Path, version: str, *args: Any, **kwargs: Any):
self.welcomed = False
self.parent_dir = parent_dir
self.version = version
self.owner_channels = [
int(x) for x in get_config_value("VALENTINA_OWNER_CHANNELS").split(",")
]
self.owner_channels = [int(x) for x in ValentinaConfig().owner_channels.split(",")]

# Load Cogs
# #######################
Expand Down
10 changes: 9 additions & 1 deletion src/valentina/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"""Utility functions for Valentina."""

from .config import ValentinaConfig
from .console import console
from .helpers import random_num
from .logging import InterceptHandler, instantiate_logger

__all__ = ["Context", "InterceptHandler", "console", "instantiate_logger", "random_num"]
__all__ = [
"Context",
"InterceptHandler",
"ValentinaConfig",
"console",
"instantiate_logger",
"random_num",
]
39 changes: 30 additions & 9 deletions src/valentina/utils/config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
"""Gather configuration from environment variables."""

import os
from pathlib import Path
from typing import ClassVar

from dotenv import dotenv_values
from confz import BaseConfig, ConfigSources, EnvSource

DIR = Path(__file__).parents[3].absolute()
CONFIG = {
**dotenv_values(DIR / ".env"), # load shared variables
**dotenv_values(DIR / ".env.secrets"), # load sensitive variables
**os.environ, # override loaded values with environment variables
}
for k, v in CONFIG.items():
CONFIG[k] = v.replace('"', "").replace("'", "").replace(" ", "")


#### NEW CONFIG ####
class ValentinaConfig(BaseConfig): # type: ignore [misc]
"""Valentina configuration."""

aws_access_key_id: str | None = None
aws_secret_access_key: str | None = None
discord_token: str
github_repo: str | None = None
github_token: str | None = None
guilds: str
log_file: str = "/valentina/valentina.log"
log_level_aws: str = "INFO"
log_level_http: str = "WARNING"
log_level: str = "INFO"
mongo_database_name: str = "valentina"
mongo_uri: str = "mongodb://localhost:27017"
owner_channels: str
owner_ids: str | None = None
s3_bucket_name: str | None = None
test_mongodb_uri: str = "mongodb://localhost:27017"
test_mongodb_db: str = "valentina-test-db"

CONFIG_SOURCES: ClassVar[ConfigSources | None] = [
EnvSource(prefix="VALENTINA_", file=DIR / ".env", allow_all=True),
EnvSource(prefix="VALENTINA_", file=DIR / ".env.secrets", allow_all=True),
]
8 changes: 4 additions & 4 deletions src/valentina/utils/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
RollStatistic,
User,
)
from valentina.utils.helpers import get_config_value
from valentina.utils import ValentinaConfig


def test_db_connection() -> bool:
"""Test the database connection using pymongo."""
logger.debug("DB: Testing connection...")
mongo_uri = get_config_value("VALENTINA_MONGO_URI")
mongo_uri = ValentinaConfig().mongo_uri

try:
client: pymongo.MongoClient = pymongo.MongoClient(mongo_uri, serverSelectionTimeoutMS=1800)
Expand All @@ -41,8 +41,8 @@ async def init_database(client=None, database=None) -> None: # type: ignore [no
database (AsyncIOMotorClient, optional): The database. Defaults to None.
"""
logger.debug("DB: Initializing...")
mongo_uri = get_config_value("VALENTINA_MONGO_URI")
db_name = get_config_value("VALENTINA_MONGO_DATABASE_NAME")
mongo_uri = ValentinaConfig().mongo_uri
db_name = ValentinaConfig().mongo_database_name

# Create Motor client
if not client:
Expand Down
25 changes: 0 additions & 25 deletions src/valentina/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
from urllib.parse import urlencode

from aiohttp import ClientSession
from loguru import logger
from numpy.random import default_rng

from valentina.constants import MaxTraitValue, XPMultiplier, XPNew
from valentina.utils import errors
from valentina.utils.config import CONFIG

_rng = default_rng()

Expand All @@ -21,29 +19,6 @@ def random_num(ceiling: int = 100) -> int:
return _rng.integers(1, ceiling + 1)


def get_config_value(key: str, default: str | None = None, pass_none: bool = False) -> str:
"""Get an environment variable and check if it exists.
Args:
key (str): The name of the config variable.
default (str, optional): The default value to use if the key is not set. Defaults to None.
pass_none (bool, optional): Whether to pass None if the key does not exist. Defaults to False.
Returns:
str: The value of the config variable.
"""
value = CONFIG.get(key, default)

if value is None and not pass_none:
logger.error(f"Config variable {key} is not set.")
raise errors.MissingConfigurationError(key)

if not value:
return None

return value.strip().lstrip('"').rstrip('"')


async def fetch_random_name(
gender: str | None = None, country: str = "us", results: int = 1
) -> list[tuple[str, str]] | tuple[str, str]: # pragma: no cover
Expand Down
Loading

0 comments on commit 7e6988a

Please sign in to comment.