Compare commits

...

2 Commits

Author SHA1 Message Date
177ec2c783 fix(deps): update dependency webex-bot to v1
Some checks failed
Enforce Conventional Commit PR Title / Validate PR Title (pull_request_target) Successful in 8s
CI / ci (pull_request) Failing after 47s
2025-06-06 19:39:25 +02:00
13097b36fb fix(lint): Fix linting issues (#487)
Some checks failed
Security / sonarqube (push) Failing after 36s
Security / snyk (push) Successful in 1m1s
This pull request focuses on improving the documentation and readability of the Webex meme bot application by adding docstrings and minor formatting adjustments. Here's a breakdown of the changes:

*   **Docstrings:**
    *   Added module-level docstrings to `app/close.py`, `app/img.py`, and `app/main.py` providing a high-level overview of the purpose of each module.
    *   Added docstrings to classes (`ExitCommand`, `MakeMemeCommand`, `MakeMemeCallback`) describing their role.
    *   Added docstrings to methods within those classes (`__init__`, `pre_execute`, `execute`, `post_execute`) explaining their functionality, arguments, and return values where applicable. The `get_templates` and `format_meme_string` functions in `app/img.py` have been documented as well.
*   **Formatting:**
    *   Added a line break before the return type annotation in function definitions (e.g., `def execute(...) -> Response:`).
    *   Added the disable comment `# pylint: disable=line-too-long` to a line in `app/meme.py` to disable pylint for that line.
    *   Added the disable comment `# pylint: disable=unused-argument` to the `pre_execute`, `execute`, and `post_execute` methods to disable pylint checks about unused arguments. This is because these methods are part of an interface and must have the same signature even if some arguments are unused.
*   **Variable Naming:**
    *   Renamed the `vars` dictionary to `env_vars` in `tests/test_config.py` for better clarity.
*   **Test Update:**
    *   Added a docstring to the `test_config` function in `tests/test_config.py` to explain its functionality.
*   **Imports Update:**
    *   Updated imports in `tests/test_config.py` to disable pylint for wrong-import-position errors using `# pylint: disable=wrong-import-position`.

In essence, these changes enhance the maintainability and understandability of the codebase through comprehensive documentation and minor code style improvements.

Reviewed-on: #487
2025-06-06 19:39:11 +02:00
9 changed files with 86 additions and 52 deletions

View File

@ -40,11 +40,15 @@ jobs:
- name: Install dependencies
run: uv sync
# - name: Lint
# run: |
# uv run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
# cat lintreport.txt
- name: Lint
run: |
uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/ # --output=lintreport.txt
cat lintreport.txt
uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/
- name: Unit Test
run: |

View File

@ -1,8 +1,13 @@
"""Command module for handling the 'exit' command in the Webex meme bot."""
from webex_bot.models.command import Command
class ExitCommand(Command):
"""Command to handle the 'exit' command in the Webex meme bot."""
def __init__(self) -> None:
"""Initialize the ExitCommand with command keyword and help message."""
super().__init__(
command_keyword="exit",
help_message="Exit",
@ -10,11 +15,14 @@ class ExitCommand(Command):
)
self.sender: str = ""
def pre_execute(self, message, attachment_actions, activity) -> None:
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument
"""Pre-execution logic for the exit command."""
return
def execute(self, message, attachment_actions, activity) -> None:
def execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument
"""Execute the exit command."""
return
def post_execute(self, message, attachment_actions, activity) -> None:
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument
"""Post-execution logic for the exit command."""
return

View File

@ -1,3 +1,5 @@
"""Generates meme images using the memegen.link API."""
import requests
CHAR_REPLACEMENTS: list = [
@ -17,6 +19,11 @@ CHAR_REPLACEMENTS: list = [
def get_templates() -> list[dict]:
"""Fetches available meme templates from the memegen.link API.
Returns:
list[dict]: A list of dictionaries containing meme template information.
"""
url: str = "https://api.memegen.link/templates"
req: requests.Response = requests.get(url=url, timeout=10)
req.raise_for_status()
@ -40,6 +47,14 @@ def get_templates() -> list[dict]:
def format_meme_string(input_string: str) -> str:
"""Formats a string for use in a meme image URL.
Args:
input_string (str): The string to format.
Returns:
str: The formatted string suitable for meme image URLs.
"""
# https://memegen.link/#special-characters
out_string: str = input_string
for char_replacement in CHAR_REPLACEMENTS:
@ -48,6 +63,16 @@ def format_meme_string(input_string: str) -> str:
def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
"""Generates a meme image URL using the memegen.link API.
Args:
template (str): The template identifier in the format "name.ext".
top_str (str): The text for the top line of the meme.
btm_str (str): The text for the bottom line of the meme.
Returns:
str: The complete URL for the meme image.
"""
tmpl_name: str
tmpl_ext: str
tmpl_name, tmpl_ext = template.split(".")
@ -55,7 +80,5 @@ def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
top_str = format_meme_string(top_str)
btm_str = format_meme_string(btm_str)
url: str = (
f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
)
url: str = f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
return url

View File

@ -1,5 +1,7 @@
#!/usr/local/bin/python3
"""Main entry point for the Webex Bot application."""
from webex_bot.webex_bot import WebexBot
from app import close, meme
@ -18,6 +20,7 @@ def create_bot() -> WebexBot:
def main() -> None:
"""Main function to run the Webex Bot."""
bot: WebexBot = create_bot()
bot.add_command(meme.MakeMemeCommand())
bot.add_command(close.ExitCommand())

View File

@ -1,3 +1,5 @@
"""Generates meme images using the memegen.link API."""
from webex_bot.models.command import Command
from webex_bot.models.response import Response, response_from_adaptive_card
from webexteamssdk.models.cards import (
@ -22,6 +24,7 @@ class MakeMemeCommand(Command):
"""Class for initial Webex interactive card."""
def __init__(self) -> None:
"""Initialize the MakeMemeCommand with command keyword and help message."""
super().__init__(
command_keyword="/meme",
help_message="Make a Meme",
@ -29,10 +32,12 @@ class MakeMemeCommand(Command):
delete_previous_message=True,
)
def pre_execute(self, message, attachment_actions, activity) -> None:
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument
"""Pre-execution logic for the MakeMemeCommand."""
return
def execute(self, message, attachment_actions, activity) -> Response:
def execute(self, message, attachment_actions, activity) -> Response: # pylint: disable=unused-argument
"""Execute the MakeMemeCommand and return an adaptive card."""
card_body: list = [
ColumnSet(
columns=[
@ -45,13 +50,13 @@ class MakeMemeCommand(Command):
size=FontSize.MEDIUM,
),
TextBlock(
"This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.",
"This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.", # pylint: disable=line-too-long
weight=FontWeight.LIGHTER,
size=FontSize.SMALL,
wrap=True,
),
TextBlock(
"Both fields are required. If you do not want to specify a value, please type a space.",
"Both fields are required. If you do not want to specify a value, please type a space.", # pylint: disable=line-too-long
weight=FontWeight.LIGHTER,
size=FontSize.SMALL,
wrap=True,
@ -68,10 +73,7 @@ class MakeMemeCommand(Command):
Choices(
id="meme_type",
isMultiSelect=False,
choices=[
Choice(title=x["name"], value=x["choiceval"])
for x in TEMPLATES
],
choices=[Choice(title=x["name"], value=x["choiceval"]) for x in TEMPLATES],
),
Text(id="text_top", placeholder="Top Text", maxLength=100),
Text(
@ -103,6 +105,7 @@ class MakeMemeCallback(Command):
"""Class to process user data and return meme."""
def __init__(self) -> None:
"""Initialize the MakeMemeCallback with command keyword and help message."""
super().__init__(
card_callback_keyword="make_meme_callback_rbamzfyx",
delete_previous_message=True,
@ -113,7 +116,8 @@ class MakeMemeCallback(Command):
self.meme: str = ""
self.meme_filename: str = ""
def pre_execute(self, message, attachment_actions, activity) -> str:
def pre_execute(self, message, attachment_actions, activity) -> str: # pylint: disable=unused-argument
"""Pre-execution logic for the MakeMemeCallback."""
self.meme: str = attachment_actions.inputs.get("meme_type")
self.text_top: str = attachment_actions.inputs.get("text_top")
self.text_bottom: str = attachment_actions.inputs.get("text_bottom")
@ -127,13 +131,12 @@ class MakeMemeCallback(Command):
return "Generating your meme..."
def execute(self, message, attachment_actions, activity) -> Response | None:
def execute(self, message, attachment_actions, activity) -> Response | None: # pylint: disable=unused-argument
"""Execute the MakeMemeCallback and return a response with the meme image."""
if self.error:
return None
self.meme_filename: str = img.generate_api_url(
self.meme, self.text_top, self.text_bottom
)
self.meme_filename: str = img.generate_api_url(self.meme, self.text_top, self.text_bottom)
msg: Response = Response(
attributes={
"roomId": activity["target"]["globalId"],
@ -143,5 +146,6 @@ class MakeMemeCallback(Command):
)
return msg
def post_execute(self, message, attachment_actions, activity) -> None:
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument
"""Post-execution logic for the MakeMemeCallback."""
return

View File

@ -8,7 +8,7 @@ authors = [
]
requires-python = ">=3.11.2"
dependencies = [
"webex-bot<1.0.0,>=0.5.2",
"webex-bot<1.1.0,>=1.0.3",
"pillow<12.0.0,>=11.0.0",
"astroid<=3.3.10",
]
@ -32,3 +32,6 @@ includes = []
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.black]
line-length = 120

View File

@ -2,19 +2,22 @@
import os
vars: dict = {
env_vars: dict = {
"APP_VERSION": "dev",
"WEBEX_API_KEY": "testing",
}
for var, value in vars.items():
for var, value in env_vars.items():
os.environ[var] = value
# needs to be imported AFTER environment variables are set
from app.config import config # pragma: no cover # noqa: E402
from app.config import (
config,
) # pylint: disable=wrong-import-position # pragma: no cover # noqa: E402
def test_config() -> None:
assert config.webex_token == vars["WEBEX_API_KEY"]
assert config.version == vars["APP_VERSION"]
"""Test the configuration settings."""
assert config.webex_token == env_vars["WEBEX_API_KEY"]
assert config.version == env_vars["APP_VERSION"]

View File

@ -29,8 +29,4 @@ def test_error_false() -> None:
callback.text_top = "TEST"
callback.text_bottom = "TEST"
result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}})
assert (
isinstance(result, Response)
and result.roomId == "TEST"
and result.files[0] == callback.meme_filename
)
assert isinstance(result, Response) and result.roomId == "TEST" and result.files[0] == callback.meme_filename

28
uv.lock generated
View File

@ -228,15 +228,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
]
[[package]]
name = "future"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" },
]
[[package]]
name = "humanfriendly"
version = "10.0"
@ -579,17 +570,17 @@ wheels = [
[[package]]
name = "webex-bot"
version = "0.5.2"
version = "1.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backoff" },
{ name = "coloredlogs" },
{ name = "webexteamssdk" },
{ name = "webexpythonsdk" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b6/29/fcfe46ea80508a2a8584466b41382cb709afdbbabfbedcab189af7f79910/webex_bot-0.5.2.tar.gz", hash = "sha256:6b381d4ed0ba500d5f1d3e96a68db599ac38466f66d98afda8762cf66138f9ff", size = 27805, upload-time = "2024-08-21T09:20:59.713Z" }
sdist = { url = "https://files.pythonhosted.org/packages/35/a5/beae32cfe2f42fc23d6beb850314fbab232d8b3c12b3f4752dfe61fe2709/webex_bot-1.0.3.tar.gz", hash = "sha256:5813be91563200953aea6ee52da8a1a4d5a0369cb5b9f96bf71c592378eb9600", size = 30000, upload-time = "2025-06-04T14:33:28.964Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/59/96a07e5d8f9ab7f13e59fec3491e46b605bc9ea9a3b0f03de5356049e915/webex_bot-0.5.2-py2.py3-none-any.whl", hash = "sha256:1ccde69de4f44bf4ad4d3c9dcc41666c09d0ff40326155f13a4213732352783e", size = 21030, upload-time = "2024-08-21T09:20:58.314Z" },
{ url = "https://files.pythonhosted.org/packages/e0/b4/ad4ce0bbd01248f949281a9831862e73ec20eef0b1b4c697a4b60802e6cf/webex_bot-1.0.3-py2.py3-none-any.whl", hash = "sha256:b9a326dabedda6a9bdee308ecb70f01277583a6bc92b583efea51e9211521567", size = 22352, upload-time = "2025-06-04T14:33:26.345Z" },
]
[[package]]
@ -617,7 +608,7 @@ dev = [
requires-dist = [
{ name = "astroid", specifier = "<=3.3.10" },
{ name = "pillow", specifier = ">=11.0.0,<12.0.0" },
{ name = "webex-bot", specifier = ">=0.5.2,<1.0.0" },
{ name = "webex-bot", specifier = ">=1.0.3,<1.1.0" },
]
[package.metadata.requires-dev]
@ -632,18 +623,17 @@ dev = [
]
[[package]]
name = "webexteamssdk"
version = "1.6.1"
name = "webexpythonsdk"
version = "2.0.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "future" },
{ name = "pyjwt" },
{ name = "requests" },
{ name = "requests-toolbelt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/5b/f6609eb1f1aeff5952c9c065b6192af2adbb5d10eabab2e9bcef17dbd5dc/webexteamssdk-1.6.1.tar.gz", hash = "sha256:bbc7672f381b26fb22d0d03f87d131a2fa1e7d54c2f37f2e4cd28d725b8b5dfb", size = 61925, upload-time = "2022-06-07T15:59:51.514Z" }
sdist = { url = "https://files.pythonhosted.org/packages/84/11/1e4e50b36228c6f40d943adc3a46b94f20864a91784e51624ad12880abba/webexpythonsdk-2.0.4.tar.gz", hash = "sha256:8103193460bb9da51b7873654f4591fc265a336751b49f372fb3b584c440c538", size = 66886, upload-time = "2025-01-22T17:12:48.576Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/48/ee89700f2255c56efee55cc341a7c173b9e1fc866f7eba358638a4becbb1/webexteamssdk-1.6.1-py3-none-any.whl", hash = "sha256:52a7f9d515cd3d53a853e679e16572ec6ca036a223e35b14fea14c99f492a6a4", size = 113528, upload-time = "2022-06-07T15:59:49.773Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a2/56c2848eb73965b70472e156650031f84ad8bc7a442b3c0c7a4846c04514/webexpythonsdk-2.0.4-py3-none-any.whl", hash = "sha256:ee8845dc79fc9b296a9e0080d1dffd9565a0116ca82b97796225057a7d22e285", size = 149107, upload-time = "2025-01-22T17:12:45.279Z" },
]
[[package]]