-
-
Notifications
You must be signed in to change notification settings - Fork 43
refactor: move constants to assets/data/constants.toml #997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR refactors all hardcoded constants into a TOML configuration, implements a loader to dynamically assign them in the Constants class, and updates tests to reference the new CONST instance with added coverage for loaded values. Class diagram for refactored Constants loading from TOMLclassDiagram
class Constants {
+EMBED_COLORS: dict[str, int]
+EMBED_ICONS: dict[str, str]
+EMBED_MAX_NAME_LENGTH: int
+EMBED_MAX_DESC_LENGTH: int
+EMBED_MAX_FIELDS: int
+EMBED_TOTAL_MAX: int
+EMBED_FIELD_VALUE_LENGTH: int
+NICKNAME_MAX_LENGTH: int
+CONTEXT_MENU_NAME_LENGTH: int
+SLASH_CMD_NAME_LENGTH: int
+SLASH_CMD_MAX_DESC_LENGTH: int
+SLASH_CMD_MAX_OPTIONS: int
+SLASH_OPTION_NAME_LENGTH: int
+ACTION_ROW_MAX_ITEMS: int
+SELECTS_MAX_OPTIONS: int
+SELECT_MAX_NAME_LENGTH: int
+DEFAULT_REASON: str
+DEFAULT_DELETE_AFTER: int
+SNIPPET_MAX_NAME_LENGTH: int
+SNIPPET_ALLOWED_CHARS_REGEX: str
+SNIPPET_PAGINATION_LIMIT: int
+AFK_PREFIX: str
+AFK_TRUNCATION_SUFFIX: str
+EIGHT_BALL_QUESTION_LENGTH_LIMIT: int
+EIGHT_BALL_RESPONSE_WRAP_WIDTH: int
+ADD_BOOKMARK: str
+REMOVE_BOOKMARK: str
+__init__()
}
class _load_constants {
+_load_constants() dict[str, Any]
}
Constants --|> _load_constants : uses
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @electron271 - I've reviewed your changes - here's some feedback:
- Consider providing a fallback import for tomllib (e.g. tomli) or explicitly pinning Python 3.11+ in your requirements to avoid runtime import errors on older versions.
- Since
_load_constants
is called every time a newConstants
is instantiated, consider caching the loaded TOML data (e.g. as a class variable) to avoid redundant disk reads. - The unit tests assert directly against the live TOML file and can become brittle when constants change—consider mocking the file load or using a dedicated test fixture to isolate test data from production constants.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider providing a fallback import for tomllib (e.g. tomli) or explicitly pinning Python 3.11+ in your requirements to avoid runtime import errors on older versions.
- Since `_load_constants` is called every time a new `Constants` is instantiated, consider caching the loaded TOML data (e.g. as a class variable) to avoid redundant disk reads.
- The unit tests assert directly against the live TOML file and can become brittle when constants change—consider mocking the file load or using a dedicated test fixture to isolate test data from production constants.
## Individual Comments
### Comment 1
<location> `tux/utils/constants.py:7` </location>
<code_context>
+from tux.utils.config import workspace_root
+
+
+def _load_constants() -> dict[str, Any]:
+ """Load constants from the TOML configuration file."""
+ constants_path = workspace_root / "assets" / "data" / "constants.toml"
</code_context>
<issue_to_address>
Consider error handling for missing or malformed TOML files.
If the TOML file is missing or malformed, the function will raise an unhandled exception. Adding error handling could improve user feedback or allow for fallback behavior.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
def _load_constants() -> dict[str, Any]:
"""Load constants from the TOML configuration file."""
constants_path = workspace_root / "assets" / "data" / "constants.toml"
with constants_path.open("rb") as f:
return tomllib.load(f)
=======
def _load_constants() -> dict[str, Any]:
"""Load constants from the TOML configuration file.
Returns an empty dict if the file is missing or malformed.
"""
import logging
constants_path = workspace_root / "assets" / "data" / "constants.toml"
try:
with constants_path.open("rb") as f:
return tomllib.load(f)
except FileNotFoundError:
logging.error(f"Constants TOML file not found at {constants_path}")
return {}
except tomllib.TOMLDecodeError as e:
logging.error(f"Malformed TOML in constants file at {constants_path}: {e}")
return {}
>>>>>>> REPLACE
</suggested_fix>
### Comment 2
<location> `tests/unit/tux/utils/test_constants.py:44` </location>
<code_context>
- assert Constants.SNIPPET_ALLOWED_CHARS_REGEX == r"^[a-zA-Z0-9-]+$"
- assert Constants.SNIPPET_PAGINATION_LIMIT == 10
+ assert CONST.SNIPPET_MAX_NAME_LENGTH == 20
+ assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$"
+ assert CONST.SNIPPET_PAGINATION_LIMIT == 10
def test_afk_constants(self):
</code_context>
<issue_to_address>
Test coverage for regex pattern usage is missing.
Please add tests that use the regex to check both valid and invalid snippet names, ensuring the pattern works as expected.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
def test_snippet_constants(self):
"""Test snippet-related constants."""
assert CONST.SNIPPET_MAX_NAME_LENGTH == 20
assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$"
assert CONST.SNIPPET_PAGINATION_LIMIT == 10
=======
def test_snippet_constants(self):
"""Test snippet-related constants."""
assert CONST.SNIPPET_MAX_NAME_LENGTH == 20
assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$"
assert CONST.SNIPPET_PAGINATION_LIMIT == 10
def test_snippet_name_regex(self):
"""Test that the snippet name regex matches valid names and rejects invalid ones."""
import re
pattern = re.compile(CONST.SNIPPET_ALLOWED_CHARS_REGEX)
valid_names = [
"snippet1",
"Snippet-2",
"A1B2C3",
"test-123",
"abcDEF-456"
]
invalid_names = [
"snippet_1", # underscore not allowed
"snippet 1", # space not allowed
"snippet!", # exclamation not allowed
"snippet@", # special char not allowed
"snippet.name" # dot not allowed
]
for name in valid_names:
assert pattern.fullmatch(name), f"Valid name did not match: {name}"
for name in invalid_names:
assert not pattern.fullmatch(name), f"Invalid name matched: {name}"
>>>>>>> REPLACE
</suggested_fix>
### Comment 3
<location> `tux/utils/constants.py:15` </location>
<code_context>
- # Bookmark constants
- ADD_BOOKMARK = "🔖"
- REMOVE_BOOKMARK = "🗑️"
+ def __init__(self) -> None:
+ data = _load_constants()
+
</code_context>
<issue_to_address>
Consider using a flattening helper to automatically assign all constants as class attributes, reducing repetitive assignment code.
You can collapse almost all of the repetitive `self.FOO = ...` lines into a small “flattening” helper. Because `tomllib` already gives you real `int` and `str` types you can skip all of the `int(...)` and `str(...)` calls, and then automatically turn every nested key into a `UPPER_SNAKE` attribute on your class.
For example, add a small utility:
```python
def _flatten_constants(data: dict[str, Any]) -> dict[str, Any]:
"""Turn e.g. data['embed_limits']['max_name_length'] into {'EMBED_MAX_NAME_LENGTH': 256}."""
out: dict[str, Any] = {}
for section, mapping in data.items():
# drop common suffixes and singularize
prefix = (
section
.removesuffix("_limits")
.removesuffix("_config")
.removesuffix("s")
.upper()
)
for key, val in mapping.items():
name = f"{prefix}_{key}".upper()
out[name] = val
return out
```
Then your `Constants` class can become:
```python
class Constants:
def __init__(self):
data = _load_constants()
flats = _flatten_constants(data)
# bulk‐assign everything
for attr, val in flats.items():
setattr(self, attr, val)
CONST = Constants()
```
This preserves your existing attribute names, removes dozens of `self.X = int(...)` lines, and will automatically pick up any new keys you add to the TOML later.
</issue_to_address>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
def _load_constants() -> dict[str, Any]: | ||
"""Load constants from the TOML configuration file.""" | ||
constants_path = workspace_root / "assets" / "data" / "constants.toml" | ||
with constants_path.open("rb") as f: | ||
return tomllib.load(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider error handling for missing or malformed TOML files.
If the TOML file is missing or malformed, the function will raise an unhandled exception. Adding error handling could improve user feedback or allow for fallback behavior.
def _load_constants() -> dict[str, Any]: | |
"""Load constants from the TOML configuration file.""" | |
constants_path = workspace_root / "assets" / "data" / "constants.toml" | |
with constants_path.open("rb") as f: | |
return tomllib.load(f) | |
def _load_constants() -> dict[str, Any]: | |
"""Load constants from the TOML configuration file. | |
Returns an empty dict if the file is missing or malformed. | |
""" | |
import logging | |
constants_path = workspace_root / "assets" / "data" / "constants.toml" | |
try: | |
with constants_path.open("rb") as f: | |
return tomllib.load(f) | |
except FileNotFoundError: | |
logging.error(f"Constants TOML file not found at {constants_path}") | |
return {} | |
except tomllib.TOMLDecodeError as e: | |
logging.error(f"Malformed TOML in constants file at {constants_path}: {e}") | |
return {} |
def test_snippet_constants(self): | ||
"""Test snippet-related constants.""" | ||
assert Constants.SNIPPET_MAX_NAME_LENGTH == 20 | ||
assert Constants.SNIPPET_ALLOWED_CHARS_REGEX == r"^[a-zA-Z0-9-]+$" | ||
assert Constants.SNIPPET_PAGINATION_LIMIT == 10 | ||
assert CONST.SNIPPET_MAX_NAME_LENGTH == 20 | ||
assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$" | ||
assert CONST.SNIPPET_PAGINATION_LIMIT == 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Test coverage for regex pattern usage is missing.
Please add tests that use the regex to check both valid and invalid snippet names, ensuring the pattern works as expected.
def test_snippet_constants(self): | |
"""Test snippet-related constants.""" | |
assert Constants.SNIPPET_MAX_NAME_LENGTH == 20 | |
assert Constants.SNIPPET_ALLOWED_CHARS_REGEX == r"^[a-zA-Z0-9-]+$" | |
assert Constants.SNIPPET_PAGINATION_LIMIT == 10 | |
assert CONST.SNIPPET_MAX_NAME_LENGTH == 20 | |
assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$" | |
assert CONST.SNIPPET_PAGINATION_LIMIT == 10 | |
def test_snippet_constants(self): | |
"""Test snippet-related constants.""" | |
assert CONST.SNIPPET_MAX_NAME_LENGTH == 20 | |
assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$" | |
assert CONST.SNIPPET_PAGINATION_LIMIT == 10 | |
def test_snippet_name_regex(self): | |
"""Test that the snippet name regex matches valid names and rejects invalid ones.""" | |
import re | |
pattern = re.compile(CONST.SNIPPET_ALLOWED_CHARS_REGEX) | |
valid_names = [ | |
"snippet1", | |
"Snippet-2", | |
"A1B2C3", | |
"test-123", | |
"abcDEF-456" | |
] | |
invalid_names = [ | |
"snippet_1", # underscore not allowed | |
"snippet 1", # space not allowed | |
"snippet!", # exclamation not allowed | |
"snippet@", # special char not allowed | |
"snippet.name" # dot not allowed | |
] | |
for name in valid_names: | |
assert pattern.fullmatch(name), f"Valid name did not match: {name}" | |
for name in invalid_names: | |
assert not pattern.fullmatch(name), f"Invalid name matched: {name}" |
# Bookmark constants | ||
ADD_BOOKMARK = "🔖" | ||
REMOVE_BOOKMARK = "🗑️" | ||
def __init__(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider using a flattening helper to automatically assign all constants as class attributes, reducing repetitive assignment code.
You can collapse almost all of the repetitive self.FOO = ...
lines into a small “flattening” helper. Because tomllib
already gives you real int
and str
types you can skip all of the int(...)
and str(...)
calls, and then automatically turn every nested key into a UPPER_SNAKE
attribute on your class.
For example, add a small utility:
def _flatten_constants(data: dict[str, Any]) -> dict[str, Any]:
"""Turn e.g. data['embed_limits']['max_name_length'] into {'EMBED_MAX_NAME_LENGTH': 256}."""
out: dict[str, Any] = {}
for section, mapping in data.items():
# drop common suffixes and singularize
prefix = (
section
.removesuffix("_limits")
.removesuffix("_config")
.removesuffix("s")
.upper()
)
for key, val in mapping.items():
name = f"{prefix}_{key}".upper()
out[name] = val
return out
Then your Constants
class can become:
class Constants:
def __init__(self):
data = _load_constants()
flats = _flatten_constants(data)
# bulk‐assign everything
for attr, val in flats.items():
setattr(self, attr, val)
CONST = Constants()
This preserves your existing attribute names, removes dozens of self.X = int(...)
lines, and will automatically pick up any new keys you add to the TOML later.
Deploying tux with
|
Latest commit: |
46870ec
|
Status: | ✅ Deploy successful! |
Preview URL: | https://e7a04753.tux-afh.pages.dev |
Branch Preview URL: | https://assets-data-constants.tux-afh.pages.dev |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #997 +/- ##
========================================
+ Coverage 9.44% 9.58% +0.13%
========================================
Files 123 123
Lines 10406 10422 +16
Branches 1277 1277
========================================
+ Hits 983 999 +16
Misses 9330 9330
Partials 93 93
*This pull request uses carry forward flags. Click here to find out more.
☔ View full report in Codecov by Sentry. |
Description
moves the constants that used to be in constants.py to a toml file in assets/data/
Guidelines
My code follows the style guidelines of this project (formatted with Ruff)
I have performed a self-review of my own code
I have commented my code, particularly in hard-to-understand areas
I have made corresponding changes to the documentation if needed
My changes generate no new warnings
I have tested this change
Any dependent changes have been merged and published in downstream modules
I have added all appropriate labels to this PR
I have followed all of these guidelines.
How Has This Been Tested? (if applicable)
ran the help command and used the tests i implemented
Screenshots (if applicable)
Please add screenshots to help explain your changes.
Additional Information
fixes #950
Summary by Sourcery
Move all static constants into a TOML configuration file and adapt the Constants class to load them at runtime, updating unit tests accordingly.
Enhancements:
Tests: