feat: Refactor endpoint listing into blueprint

- Moved endpoint listing and searching routes (`list_endpoints`, `search_endpoints`) and helper function (`get_routes`) from `app.py` into a new blueprint at `routes/endpoints.py`.
- Added `/endpoints` URL prefix to the blueprint.
- Registered the new `endpoints_bp` blueprint in `app.py`.
- Removed the original endpoint route definitions and helper function from `app.py`.
- Updated `url_for` calls in relevant templates (`endpoints.html`, `base.html`) to reference the new blueprint endpoints (e.g., `endpoints.list_endpoints`).
- Updated `templates/changelog/changelog.html` to document this refactoring.
```
This commit is contained in:
Peter Stockings
2025-03-31 23:15:24 +11:00
parent a8fe28339b
commit b875b49eca
5 changed files with 75 additions and 50 deletions

49
app.py
View File

@@ -11,6 +11,7 @@ from routes.calendar import calendar_bp # Import the new calendar blueprint
from routes.notes import notes_bp # Import the new notes blueprint
from routes.workout import workout_bp # Import the new workout blueprint
from routes.sql_explorer import sql_explorer_bp # Import the new SQL explorer blueprint
from routes.endpoints import endpoints_bp # Import the new endpoints blueprint
from extensions import db
from utils import convert_str_to_date, generate_plot
from flask_htmx import HTMX
@@ -42,6 +43,7 @@ app.register_blueprint(calendar_bp) # Register the calendar blueprint
app.register_blueprint(notes_bp) # Register the notes blueprint
app.register_blueprint(workout_bp) # Register the workout blueprint
app.register_blueprint(sql_explorer_bp) # Register the SQL explorer blueprint (prefix defined in blueprint file)
app.register_blueprint(endpoints_bp) # Register the endpoints blueprint (prefix defined in blueprint file)
@app.after_request
def response_minify(response):
@@ -308,53 +310,6 @@ def delete_exercise(exercise_id):
db.exercises.delete_exercise(exercise_id)
return ""
def get_routes():
routes = []
for rule in app.url_map.iter_rules():
if rule.endpoint == 'static':
continue
methods = ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'}))
route_info = {
'endpoint': rule.endpoint,
'url': rule.rule,
'methods': methods,
'view_func': app.view_functions[rule.endpoint].__name__,
'doc': app.view_functions[rule.endpoint].__doc__
}
routes.append(route_info)
return routes
@app.route('/endpoints')
def list_endpoints():
"""
Lists all API endpoints available in the Flask application.
This endpoint retrieves all registered routes, excluding static routes,
and displays their details such as endpoint name, URL, allowed HTTP methods,
view function name, and a brief description.
"""
routes = get_routes()
if htmx:
return render_block(app.jinja_env, 'endpoints.html', 'content', routes=routes)
return render_template('endpoints.html', routes=routes)
@app.route('/endpoints/search')
def search_endpoints():
routes = get_routes()
search = request.args.get('search', '').lower()
if search:
routes = [
route for route in routes
if search in route['endpoint'].lower()
or search in route['url'].lower()
or search in route['methods'].lower()
or search in route['view_func'].lower()
or (route['doc'] and search in route['doc'].lower())
]
return render_template('partials/endpoints_table.html', routes=routes)
@app.teardown_appcontext
def closeConnection(exception):
db.close_connection()

60
routes/endpoints.py Normal file
View File

@@ -0,0 +1,60 @@
from flask import Blueprint, render_template, request, current_app
from jinja2_fragments import render_block
from flask_htmx import HTMX
endpoints_bp = Blueprint('endpoints', __name__, url_prefix='/endpoints')
htmx = HTMX()
def _get_routes(app):
"""Helper function to extract routes from the Flask app object."""
routes = []
for rule in app.url_map.iter_rules():
# Exclude static routes and HEAD/OPTIONS methods
if rule.endpoint == 'static':
continue
methods = ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'}))
route_info = {
'endpoint': rule.endpoint,
'url': rule.rule,
'methods': methods,
'view_func': app.view_functions[rule.endpoint].__name__,
'doc': app.view_functions[rule.endpoint].__doc__
}
routes.append(route_info)
return routes
@endpoints_bp.route('/')
def list_endpoints():
"""
Lists all API endpoints available in the Flask application.
This endpoint retrieves all registered routes, excluding static routes,
and displays their details such as endpoint name, URL, allowed HTTP methods,
view function name, and a brief description.
"""
# Pass current_app to the helper function
routes = _get_routes(current_app)
if htmx:
# Assuming 'endpoints.html' uses a block named 'content'
return render_block(current_app.jinja_env, 'endpoints.html', 'content', routes=routes)
return render_template('endpoints.html', routes=routes)
@endpoints_bp.route('/search')
def search_endpoints():
"""Searches and filters endpoints based on a query."""
# Pass current_app to the helper function
routes = _get_routes(current_app)
search = request.args.get('search', '').lower()
if search:
routes = [
route for route in routes
if search in route['endpoint'].lower()
or search in route['url'].lower()
or search in route['methods'].lower()
or search in route['view_func'].lower()
or (route.get('doc') and search in route['doc'].lower()) # Safer access to 'doc'
]
# Renders the partial table
return render_template('partials/endpoints_table.html', routes=routes)

View File

@@ -172,8 +172,9 @@
</svg>
<span class="ml-3">SQL Explorer</span>
</a>
<a hx-get="{{ url_for('list_endpoints') }}" hx-push-url="true" hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('list_endpoints')) }} page-link"
<a hx-get="{{ url_for('endpoints.list_endpoints') }}" hx-push-url="true"
hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('endpoints.list_endpoints')) }} page-link"
_="on click add .hidden to #sidebar then remove .ml-64 from #main
on htmx:afterRequest go to the top of the body">
<!-- Server Icon from Heroicons -->

View File

@@ -10,6 +10,15 @@
<div class="prose max-w-none">
<p>Updates and changes to the site will be documented here, with the most recent changes listed first.</p>
<!-- New Entry for Endpoints Refactoring -->
<hr class="my-6">
<h2 class="text-xl font-semibold mb-2">March 31, 2025</h2>
<ul class="list-disc pl-5 space-y-1">
<li>Refactored endpoint listing/searching functionality into its own blueprint (`routes/endpoints.py`
with `/endpoints` prefix).</li>
<li>Updated relevant `url_for` calls in templates to use the new `endpoints.` prefix.</li>
</ul>
<!-- New Entry for SQL Explorer Refactoring -->
<hr class="my-6">
<h2 class="text-xl font-semibold mb-2">March 31, 2025</h2>

View File

@@ -11,7 +11,7 @@
<!-- Optional: Search or Filter functionality with HTMX -->
<div class="mb-4">
<input type="text" id="search" name="search" placeholder="Search endpoints..."
class="w-full p-2 border border-gray-300 rounded" hx-get="{{ url_for('search_endpoints') }}"
class="w-full p-2 border border-gray-300 rounded" hx-get="{{ url_for('endpoints.search_endpoints') }}"
hx-trigger="keyup changed delay:500ms, search" hx-push-url="true" hx-target="#endpoints-table"
hx-include="[name='search']">
</div>