-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor Matplotlib API to Professional Standard #1
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| .env | ||
| .git | ||
| .github | ||
| tests/ | ||
| venv/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| name: Python CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
| pull_request: | ||
| branches: [ "main" ] | ||
|
|
||
| jobs: | ||
| build: | ||
|
|
||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Set up Python 3.12 | ||
| uses: actions/setup-python@v3 | ||
| with: | ||
| python-version: "3.12" | ||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install -r requirements.txt | ||
| - name: Test with pytest | ||
| run: | | ||
| export PYTHONPATH=$PYTHONPATH:. | ||
| pytest | ||
|
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set The test suite requires the 🔧 Proposed fix - name: Test with pytest
+ env:
+ API_KEY: test-key
run: |
export PYTHONPATH=$PYTHONPATH:.
pytest🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *.so | ||
| .env | ||
| venv/ | ||
| .pytest_cache/ | ||
| .coverage | ||
| htmlcov/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| FROM python:3.12-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY requirements.txt . | ||
|
|
||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| COPY . . | ||
|
|
||
| # Expose the port | ||
| EXPOSE 5150 | ||
|
|
||
| # Run the application with Gunicorn | ||
| # Bind to 0.0.0.0:5150 | ||
| CMD ["gunicorn", "--bind", "0.0.0.0:5150", "src.app:app"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| install: | ||
| pip install -r requirements.txt | ||
|
|
||
| test: | ||
| export PYTHONPATH=$PYTHONPATH:. && pytest | ||
|
|
||
| run: | ||
| export API_KEY=dev-key && python -m src.app | ||
|
|
||
| clean: | ||
| rm -rf __pycache__ | ||
| rm -rf src/__pycache__ | ||
| rm -rf tests/__pycache__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,131 @@ | ||
| # Matplotlib Graph Render API | ||
|
|
||
| This is a minimal Flask API to execute Python code with matplotlib and numpy and return a generated graph as a PNG image. | ||
|  | ||
|  | ||
|  | ||
|
|
||
| ## Usage | ||
| A robust, containerized Flask API that allows you to execute Python code remotely to generate Matplotlib graphs. This service is designed for headless environments and includes authentication and basic logging. | ||
|
|
||
| - POST to `/render-matplotlib` with JSON body: | ||
| `{ "code": "YOUR PYTHON CODE HERE" }` | ||
| ## Features | ||
|
|
||
| - Returns: PNG image (mimetype `image/png`) | ||
| - **Headless Rendering:** Uses Matplotlib's Agg backend to render graphs without a display. | ||
| - **Secure-ish Execution:** Executes user-provided Python code in a restricted scope (see Security Note). | ||
| - **Authentication:** Protects endpoints with API Key authentication. | ||
| - **Logging:** Structured logging for monitoring and debugging. | ||
| - **Dockerized:** Ready for deployment using Docker. | ||
| - **Tested:** Comprehensive test suite using `pytest`. | ||
|
|
||
| ## Example Python code to send | ||
| ## Project Structure | ||
|
|
||
| ``` | ||
| import numpy as np | ||
| import matplotlib.pyplot as plt | ||
| x = np.linspace(0, 10, 1000) | ||
| y = np.sin(x) | ||
| plt.plot(x, y) | ||
| plt.title('Sine Wave') | ||
| . | ||
| ├── src/ | ||
| │ ├── app.py # Main Flask application | ||
| │ ├── config.py # Configuration management | ||
| │ └── executor.py # Code execution logic | ||
| ├── tests/ # Unit tests | ||
| ├── Dockerfile # Container definition | ||
| ├── Makefile # Convenience commands | ||
| └── requirements.txt # Python dependencies | ||
| ``` | ||
|
Comment on lines
20
to
30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix markdownlint issues (code fence language + list indentation). These are flagged by MD040 and MD007 and can break doc linting in CI. ✍️ Proposed README formatting fixes-```
+```text
.
├── src/
│ ├── app.py # Main Flask application
│ ├── config.py # Configuration management
│ └── executor.py # Code execution logic
├── tests/ # Unit tests
├── Dockerfile # Container definition
├── Makefile # Convenience commands
└── requirements.txt # Python dependencies@@
In
|
||
|
|
||
| ## Deploy on Render | ||
| ## Getting Started | ||
|
|
||
| 1. Upload these files to GitHub | ||
| 2. Connect GitHub repo to Render.com, create a new Web Service | ||
| 3. Use `pip install -r requirements.txt` as the build command | ||
| 4. Use `python app.py` as the start command | ||
| 5. Set PORT environment variable to `$PORT` if required by Render | ||
| ### Prerequisites | ||
|
|
||
| --- | ||
| **Security Note:** This runs arbitrary code, so use with care. | ||
| - Python 3.10+ | ||
| - Pip | ||
|
|
||
| ### Installation | ||
|
|
||
| 1. Clone the repository: | ||
| ```bash | ||
| git clone https://github.com/your-username/matplotlib-render-api.git | ||
| cd matplotlib-render-api | ||
| ``` | ||
|
|
||
| 2. Install dependencies: | ||
| ```bash | ||
| make install | ||
| # or | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| 3. Set up environment variables: | ||
| Create a `.env` file in the root directory: | ||
| ```env | ||
| PORT=5150 | ||
| API_KEY=your-secret-api-key | ||
| DEBUG=True | ||
| ``` | ||
|
|
||
| ### Running Locally | ||
|
|
||
| ```bash | ||
| make run | ||
| # or | ||
| export API_KEY=my-secret-key | ||
| python -m src.app | ||
| ``` | ||
|
|
||
| The server will start at `http://0.0.0.0:5150`. | ||
|
|
||
| ## API Documentation | ||
|
|
||
| ### 1. Health Check | ||
|
|
||
| **GET** `/` | ||
|
|
||
| Returns a simple status message to verify the service is running. | ||
|
|
||
| - **Response:** `200 OK` | ||
|
|
||
| ### 2. Render Graph | ||
|
|
||
| **POST** `/render-matplotlib` | ||
|
|
||
| Executes the provided Python code and returns the generated graph as a PNG image. | ||
|
|
||
| - **Headers:** | ||
| - `Content-Type: application/json` | ||
| - `X-API-Key: <your-api-key>` | ||
|
|
||
| - **Body:** | ||
| ```json | ||
| { | ||
| "code": "import numpy as np\nimport matplotlib.pyplot as plt\nx = np.linspace(0, 10, 100)\nplt.plot(x, np.sin(x))\n" | ||
| } | ||
| ``` | ||
|
|
||
| - **Response:** | ||
| - `200 OK`: Returns the PNG image (MIME type `image/png`). | ||
| - `400 Bad Request`: Missing code or invalid JSON. | ||
| - `401 Unauthorized`: Invalid or missing API Key. | ||
| - `500 Internal Server Error`: Error during code execution (returns error trace). | ||
|
|
||
| ## Docker | ||
|
|
||
| Build and run the container: | ||
|
|
||
| ```bash | ||
| docker build -t matplotlib-api . | ||
| docker run -p 5150:5150 -e API_KEY=secret matplotlib-api | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| Run the test suite using `pytest`: | ||
|
|
||
| ```bash | ||
| make test | ||
| # or | ||
| export PYTHONPATH=$PYTHONPATH:. | ||
| pytest | ||
| ``` | ||
|
|
||
| ## Security Note | ||
|
|
||
| **⚠️ Warning:** This application uses Python's `exec()` function to execute arbitrary code sent by the client. While basic sandboxing is attempted, **it is not secure against malicious attacks**. | ||
| - **Do not** expose this API publicly without strict firewalls and trusted clients. | ||
| - The current implementation allows imports to facilitate library usage, which increases risk. | ||
| - For a truly secure environment, consider running the execution logic inside an isolated ephemeral container (e.g., AWS Lambda, Firecracker microVMs). | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| flask | ||
| matplotlib | ||
| numpy | ||
| flask==3.0.0 | ||
| matplotlib==3.8.2 | ||
| numpy==1.26.2 | ||
| python-dotenv==1.0.0 | ||
| pytest==7.4.3 | ||
| gunicorn==21.2.0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: GHSA-hc5x-x2vx-497g (alias CVE-2024-6827) is a high-severity HTTP request/response smuggling issue in Gunicorn caused by improper validation of the
Sources: GitHub Advisory Database [1], NVD entry [2], GitLab advisory [3], Gunicorn 22.0.0 release notes [4]. 🌐 Web query:
💡 Result: GHSA-w3h3-4rj7-4ph4 (CVE-2024-1135) — Gunicorn HTTP request smuggling
Sources: GitHub Advisory Database (GHSA) [1], GitLab Advisory Database [2]. 🌐 Web query:
💡 Result:
Action: If you are on 21.2.0, upgrade to >= 22.0.0 (or ideally the current 24.1.1) to remediate these CVEs. [1][2][4] Sources Citations:
Upgrade gunicorn to address known HTTP request smuggling vulnerabilities. The pinned version
Both are fixed in 22.0.0 or later. Upgrade to at least 22.0.0 (current latest is 24.1.1). 🧰 Tools🪛 OSV Scanner (2.3.2)[HIGH] 6-6: gunicorn 21.2.0: Gunicorn HTTP Request/Response Smuggling vulnerability [HIGH] 6-6: gunicorn 21.2.0: Request smuggling leading to endpoint restriction bypass in Gunicorn 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||
| from flask import Flask, request, send_file, jsonify | ||||||||||||||||||||||||||||||||||
| import traceback | ||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||
| from functools import wraps | ||||||||||||||||||||||||||||||||||
| from .config import Config | ||||||||||||||||||||||||||||||||||
| from .executor import execute_plot_code | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Configure logging | ||||||||||||||||||||||||||||||||||
| logging.basicConfig( | ||||||||||||||||||||||||||||||||||
| level=logging.INFO, | ||||||||||||||||||||||||||||||||||
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| app = Flask(__name__) | ||||||||||||||||||||||||||||||||||
| app.config.from_object(Config) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def require_api_key(f): | ||||||||||||||||||||||||||||||||||
| @wraps(f) | ||||||||||||||||||||||||||||||||||
| def decorated(*args, **kwargs): | ||||||||||||||||||||||||||||||||||
| api_key = request.headers.get('X-API-Key') | ||||||||||||||||||||||||||||||||||
| if not api_key or api_key != app.config['API_KEY']: | ||||||||||||||||||||||||||||||||||
| logger.warning("Unauthorized access attempt") | ||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Unauthorized"}), 401 | ||||||||||||||||||||||||||||||||||
| return f(*args, **kwargs) | ||||||||||||||||||||||||||||||||||
| return decorated | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @app.route('/') | ||||||||||||||||||||||||||||||||||
| def index(): | ||||||||||||||||||||||||||||||||||
| logger.info("Health check endpoint called") | ||||||||||||||||||||||||||||||||||
| return 'Matplotlib Graph Render API is live!', 200 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @app.route('/render-matplotlib', methods=['POST']) | ||||||||||||||||||||||||||||||||||
| @require_api_key | ||||||||||||||||||||||||||||||||||
| def render_matplotlib(): | ||||||||||||||||||||||||||||||||||
| logger.info("Render endpoint called") | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if not request.is_json: | ||||||||||||||||||||||||||||||||||
| logger.warning("Request content-type is not JSON") | ||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Content-Type must be application/json"}), 400 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| data = request.get_json() | ||||||||||||||||||||||||||||||||||
| code = data.get("code") | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+38
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In Flask 3.x, Pattern: catch malformed JSON (400) and wrong content type (415)from flask import request, jsonify
from werkzeug.exceptions import BadRequest, UnsupportedMediaType
`@app.post`("/items")
def items():
try:
data = request.get_json() # same as request.json (default args)
except BadRequest:
return jsonify(error="Malformed JSON"), 400
except UnsupportedMediaType:
return jsonify(error="Content-Type must be application/json"), 415
return jsonify(ok=True, data=data), 200(Types and behaviors: Pattern: “don’t throw, just return None”Use data = request.get_json(silent=True)
if data is None:
return {"error": "Missing/invalid JSON"}, 400Gotcha (repeated calls)Older Flask/Werkzeug versions cached Sources: Werkzeug References: Citations:
🌐 Web query:
💡 Result:
What happens without
Important edge case (older Flask versions):
Practical takeaways:
Sources: Handle invalid JSON bodies explicitly.
Suggested fix- data = request.get_json()
+ data = request.get_json(silent=True)
+ if data is None:
+ logger.warning("Invalid JSON payload")
+ return jsonify({"error": "Invalid JSON"}), 400📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if not code or not isinstance(code, str): | ||||||||||||||||||||||||||||||||||
| logger.warning("Render request missing code or code is not a string") | ||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Missing or invalid 'code' field"}), 400 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||
| logger.info("Executing plot code") | ||||||||||||||||||||||||||||||||||
| buf = execute_plot_code(code) | ||||||||||||||||||||||||||||||||||
| logger.info("Plot generated successfully") | ||||||||||||||||||||||||||||||||||
| return send_file(buf, mimetype='image/png') | ||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||
| logger.error(f"Error executing code: {e}") | ||||||||||||||||||||||||||||||||||
| logger.error(traceback.format_exc()) | ||||||||||||||||||||||||||||||||||
| # In production, we might want to log the full trace but hide it from the user | ||||||||||||||||||||||||||||||||||
| # keeping original behavior for now | ||||||||||||||||||||||||||||||||||
| return jsonify({"error": traceback.format_exc()}), 500 | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+54
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's verify the code exists and examine the context
if [ -f "src/app.py" ]; then
echo "=== File found, reading lines 50-65 for context ==="
sed -n '50,65p' src/app.py
else
echo "src/app.py not found"
fiRepository: 09Catho/matplotownapi Length of output: 698 🏁 Script executed: # Check if there are tests that might reference this error handling
fd -t f -e py | xargs rg -l "error executing code|traceback.format_exc" 2>/dev/null | head -20Repository: 09Catho/matplotownapi Length of output: 75 🌐 Web query:
💡 Result: What Flask does in DEBUG mode (tracebacks)
Production best practice: do not return tracebacks
Recommended error-handling pattern (hide details, log internally)Use Flask error handlers to return safe responses, while logging the real exception server-side: from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
`@app.errorhandler`(HTTPException)
def handle_http(e):
# client errors like 404/405/etc.
return jsonify(error=e.name), e.code
`@app.errorhandler`(Exception)
def handle_unexpected(e):
# server errors
app.logger.exception("Unhandled exception") # logs stack trace
return jsonify(error="Internal Server Error"), 500Key points from Flask’s error-handling rules:
If you really need tracebacks outside debug (generally: don’t)
Citations:
🏁 Script executed: # Check if DEBUG config is set up in the Flask app
rg -A 5 -B 5 "app.config|DEBUG" src/app.py | head -40Repository: 09Catho/matplotownapi Length of output: 860 🏁 Script executed: # Search for tests that might depend on the error response format
fd -t f -e py test | xargs rg -l "error|traceback|500" 2>/dev/nullRepository: 09Catho/matplotownapi Length of output: 82 🏁 Script executed: # Check the full app configuration to understand DEBUG setup
cat -n src/app.py | head -30Repository: 09Catho/matplotownapi Length of output: 1115 🏁 Script executed: # Find and read the Config class
fd -t f -name "config.py" | head -5 | xargs cat -nRepository: 09Catho/matplotownapi Length of output: 295 🏁 Script executed: # Read the test file to see what error response format is expected
cat -n tests/test_app.pyRepository: 09Catho/matplotownapi Length of output: 2251 🏁 Script executed: # Find config.py and read it
find . -name "config.py" -type f | xargs cat -nRepository: 09Catho/matplotownapi Length of output: 546 🏁 Script executed: # Also check if there's a conftest.py that sets up the test client and app
find . -name "conftest.py" -type f | xargs cat -nRepository: 09Catho/matplotownapi Length of output: 681 Return generic error responses in production; log tracebacks server-side only. Returning The DEBUG configuration is already available in your Config class, so the proposed fix using
🔒 Safer error response except Exception as e:
logger.error(f"Error executing code: {e}")
logger.error(traceback.format_exc())
# In production, we might want to log the full trace but hide it from the user
# keeping original behavior for now
return jsonify({"error": traceback.format_exc()}), 500
+ except Exception:
+ logger.exception("Error executing code")
+ if app.config.get("DEBUG"):
+ return jsonify({"error": traceback.format_exc()}), 500
+ return jsonify({"error": "Execution failed"}), 500📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.14.14)54-54: Do not catch blind exception: (BLE001) 55-55: Use Replace with (TRY400) 56-56: Use Replace with (TRY400) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if __name__ == '__main__': | ||||||||||||||||||||||||||||||||||
| app.run(host='0.0.0.0', port=app.config['PORT']) | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import os | ||
| from dotenv import load_dotenv | ||
|
|
||
| # Load environment variables from .env file if it exists | ||
| load_dotenv() | ||
|
|
||
| class Config: | ||
| """Base configuration.""" | ||
| PORT = int(os.environ.get("PORT", 5150)) | ||
| API_KEY = os.environ.get("API_KEY") | ||
| if not API_KEY: | ||
| raise ValueError("No API_KEY set for Flask application") | ||
| DEBUG = os.environ.get("DEBUG", "False").lower() == "true" | ||
|
Comment on lines
+7
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class-level validation runs at import time, breaking tests and flexibility. The
Consider deferring validation to a method or property, or using a factory pattern. 🔧 Proposed fix using a classmethod class Config:
"""Base configuration."""
PORT = int(os.environ.get("PORT", 5150))
- API_KEY = os.environ.get("API_KEY")
- if not API_KEY:
- raise ValueError("No API_KEY set for Flask application")
DEBUG = os.environ.get("DEBUG", "False").lower() == "true"
+
+ `@classmethod`
+ def get_api_key(cls):
+ api_key = os.environ.get("API_KEY")
+ if not api_key:
+ raise ValueError("No API_KEY set for Flask application")
+ return api_keyThen update consumers to call 🧰 Tools🪛 Ruff (0.14.14)12-12: Avoid specifying long messages outside the exception class (TRY003) 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import matplotlib | ||
| matplotlib.use('Agg') # For headless servers | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| import io | ||
| import traceback | ||
|
|
||
| def execute_plot_code(code: str) -> io.BytesIO: | ||
| """ | ||
| Executes the provided Python code in a restricted environment | ||
| and returns a BytesIO buffer containing the PNG image. | ||
|
|
||
| Raises Exception if code execution fails. | ||
| """ | ||
| # Setup a fresh plot context | ||
| plt.close('all') | ||
| buf = io.BytesIO() | ||
|
|
||
| # Allow only safe builtins (very basic sandboxing) | ||
| # Note: exec is dangerous. In a real production env, use better isolation. | ||
| # We allow builtins so imports work, but this is not secure. | ||
| safe_globals = {"plt": plt, "np": np} | ||
|
|
||
| try: | ||
| exec(code, safe_globals) | ||
| plt.savefig(buf, format='png', bbox_inches='tight', dpi=200) | ||
| plt.close() | ||
| buf.seek(0) | ||
| return buf | ||
| except Exception: | ||
| plt.close() | ||
|
Comment on lines
+19
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n src/executor.pyRepository: 09Catho/matplotownapi Length of output: 1213 🏁 Script executed: rg -i "timeout|signal|alarm|resource|rlimit" --type pyRepository: 09Catho/matplotownapi Length of output: 47 🏁 Script executed: rg "execute_plot_code" --type py -B 2 -A 2Repository: 09Catho/matplotownapi Length of output: 709 🏁 Script executed: rg -i "sandbox|isolation|process|subprocess|container" --type pyRepository: 09Catho/matplotownapi Length of output: 230 🏁 Script executed: cat -n src/app.pyRepository: 09Catho/matplotownapi Length of output: 2548 🏁 Script executed: rg "auth|validate|limit|rate" --type py -iRepository: 09Catho/matplotownapi Length of output: 451 🏁 Script executed: find . -name "README*" -o -name "*.md" | head -5Repository: 09Catho/matplotownapi Length of output: 76 🏁 Script executed: cat README.mdRepository: 09Catho/matplotownapi Length of output: 3620 🏁 Script executed: cat -n tests/test_app.py | head -100Repository: 09Catho/matplotownapi Length of output: 2251 Code execution lacks timeout and resource limits, enabling DoS attacks. The 🧰 Tools🪛 Ruff (0.14.14)25-25: Use of (S102) 29-29: Consider moving this statement to an (TRY300) 🤖 Prompt for AI Agents |
||
| raise | ||
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.
Update GitHub Actions to v4.
The
actions/checkout@v3andactions/setup-python@v3actions are outdated. Version 3 uses Node.js 16 which is deprecated on GitHub Actions runners.🔧 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 actionlint (1.7.10)
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
17-17: the runner of "actions/setup-python@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🤖 Prompt for AI Agents