This document describes the structure of the Flask-based API, including
run.py, the api.<domain> packages,
and the endpoint and helper modules such as
api.genealogy.persons and api.genealogy.persons_support.
run.pyapi/<domain>/__init__.pyapi/<domain>/xxx.pyThe project exposes a Flask-based API with a dynamic, modular structure:
run.py) that:
api.* packagesregister_api(app)registered_apis.json)/registered_apis and /docs./api/
(e.g. api.genealogy, api.k3, api.video_server),
each responsible for a logical area of the system.
api/genealogy/persons.py)
that declare routes via a simple contract:
def register_api(bp: Blueprint):
...
api.genealogy.support,
api.genealogy.persons_support) holding shared logic
and DB helpers.
Thanks to auto-discovery, new domains and new endpoints can be added
without modifying run.py.
Conceptual directory layout:
project/
├─ run.py # Main Flask app + auto-registration + docs
├─ registered_apis.json # Auto-generated registry of endpoints
└─ api/
├─ genealogy/
│ ├─ __init__.py # Blueprint + auto-import of genealogy endpoints
│ ├─ persons.py # /genealogy/persons endpoint(s)
│ ├─ persons_support.py # helper functions used by persons.py
│ ├─ support.py # shared DB helpers (e.g. fetch_one, fetch_all)
│ └─ ... # other genealogy-related endpoints
├─ k3/
│ ├─ __init__.py
│ └─ ...
└─ video_server/
├─ __init__.py
└─ ...
Every api/<domain> folder is a Python package with:
__init__.py → defines a Blueprint and auto-registers its endpoint modules.*.py → endpoint modules that implement HTTP handlers.*_support.py (optional) → helper/service modules used by those endpoints.run.pyrun.py is responsible for creating and configuring the Flask app:
Flask application instance and sets secret_key.OAuth() with the app.rforssen.net, with support for credentials.
To make sure imports like import api.genealogy work consistently,
run.py adds:
api
to sys.path.
api.* packages
Using pkgutil.walk_packages, run.py scans the
./api directory and collects all packages into
API_PACKAGES (e.g. api.genealogy,
api.k3, api.video_server).
registered_tree – internal registration tree
run.py maintains a nested dictionary called
registered_tree that records:
api.register_api(app) was found and called,register_api), orapi, which endpoints it owns and which HTTP methods they support.
Later, this tree is stored inside registered_apis.json.
register_api(app) on each package
For each discovered api.<domain> package:
run.py imports it via importlib.import_module(pkg_name).register_api(app) function, run.py calls it.register_api(bp), and
If a package has no register_api, or an exception occurs,
this is recorded in registered_tree with a status like
"skipped" or "error".
After all packages have registered their routes, run.py inspects
Flask's URL map:
app.url_map.iter_rules()./genealogy/persons)
Routes are grouped by module and inserted into the appropriate node of
registered_tree so that each api.<domain>.<module>
lists its own endpoints.
registered_apis.json and helper endpoints
run.py writes a JSON file, registered_apis.json, containing:
registered_apis: a hierarchical view of packages and endpoints.flask_routes: a flat list of all routes with metadata.It also exposes a few helper endpoints:
GET / —
Simple health check returning "Flask API is running.".
GET /registered_apis —
Returns the contents of registered_apis.json.
GET /docs —
Builds a minimal OpenAPI-like document from the logged routes.
This can be used as input for Swagger UI or other tooling.
api/<domain>/__init__.py
Each domain package (for example api.genealogy) defines:
register_api(app) that:
register_api(bp) function when present,Example: api/genealogy/__init__.py
from flask import Blueprint
import importlib
import pkgutil
import os
bp = Blueprint("genealogy", __name__, url_prefix="/genealogy")
def register_api(app):
package_path = os.path.dirname(__file__)
package_name = __name__ # 'api.genealogy'
for _, module_name, is_pkg in pkgutil.iter_modules([package_path]):
full_module_name = f"{package_name}.{module_name}"
try:
mod = importlib.import_module(full_module_name)
if hasattr(mod, "register_api"):
mod.register_api(bp)
except Exception as e:
print(f"⚠️ Failed to import {full_module_name}: {e}")
app.register_blueprint(bp)
All modules under api.genealogy that implement
register_api(bp) automatically attach their routes to this
Blueprint and are exposed at URLs starting with /genealogy/....
api/<domain>/xxx.pyEach endpoint module is responsible for a set of related HTTP routes. The only requirement is that it exposes a function:
def register_api(bp: Blueprint):
# define one or more routes on bp
Inside this function, routes are bound to the domain Blueprint with
@bp.route(...).
api.genealogy.persons
The persons.py module defines the /genealogy/persons
endpoint, using helper functions moved to
api.genealogy.persons_support.
from flask import Blueprint, request, jsonify
from api.genealogy.support import fetch_one, fetch_all, getGenDBconnection
from api.genealogy.persons_support import generate_columns_str, getFamilies, getReadAlso, getFamObj
print("✅ persons.py loaded")
def register_api(bp: Blueprint):
@bp.route("/persons")
def persons_handler():
log = []
action = request.args.get("action")
id = request.args.get("id")
if action != "onload":
return jsonify({"error": f"Action {action} not implemented", "log": log}), 400
conn = getGenDBconnection(log)
if conn is None:
return jsonify({"error": "Database connection failed", "log": log}), 500
cursor = conn.cursor(dictionary=True, buffered=True)
response = {
"log": log,
"person": {},
"childhoodFamily": {},
"families": [],
"notes": [],
"ancestorRelation": []
}
try:
response["person"] = fetch_one(cursor,
"SELECT * FROM tblIndividual WHERE IndKey = %s",
id, log)
response["person"]["Facts"] = fetch_all(cursor,
"SELECT * FROM tblFacts WHERE IndFamKey = %s",
(id,), log)
response["person"]["Fbok"] = fetch_one(cursor,
"SELECT * FROM tblFbok WHERE IndKey = %s",
id, log)
response["person"]["Dbok"] = fetch_one(cursor,
"SELECT * FROM tblDbok WHERE IndKey = %s",
id, log)
fam_register = fetch_one(cursor,
"SELECT * FROM tblRelation WHERE IndKey = %s",
id, log)
if fam_register:
response["childhoodFamily"] = getFamObj(fam_register["FamKey"], cursor)
response["halfFamilies"] = {"mother": [], "father": []}
if response["childhoodFamily"].get("father") \
and response["childhoodFamily"]["father"].get("IndKey"):
father_id = response["childhoodFamily"]["father"]["IndKey"]
halfFamilies = fetch_all(cursor,
"SELECT * FROM tblFamily WHERE FamSpouse1 = %s",
(father_id,), log)
log.append(halfFamilies)
for halfFamily in halfFamilies:
if halfFamily["FamKey"] != fam_register["FamKey"]:
response["halfFamilies"]["father"].append(
getFamObj(halfFamily["FamKey"], cursor))
if response["childhoodFamily"].get("mother") \
and response["childhoodFamily"]["mother"].get("IndKey"):
mother_id = response["childhoodFamily"]["mother"]["IndKey"]
halfFamilies = fetch_all(cursor,
"SELECT * FROM tblFamily WHERE FamSpouse2 = %s",
(mother_id,), log)
log.append(halfFamilies)
for halfFamily in halfFamilies:
if halfFamily["FamKey"] != fam_register["FamKey"]:
response["halfFamilies"]["mother"].append(
getFamObj(halfFamily["FamKey"], cursor))
response["notes"] = fetch_all(cursor,
"SELECT * FROM tblNotes WHERE IndFamKey = %s",
(id,), log)
response["families"] = getFamilies(cursor, response["person"], log)
response.update(getReadAlso(response["person"], cursor, log))
except Exception as e:
log.append(f"❌ Exception: {str(e)}")
return jsonify({"error": "Query error", "log": log}), 500
finally:
conn.close()
return jsonify(response)
Endpoint modules are focused on:
Helpers and shared logic are kept in separate files so that endpoint modules remain readable and easy to test. These modules generally do not import Flask; they operate purely on data.
api.genealogy.supportThis module typically contains generic DB utilities such as:
getGenDBconnection(log) — Open a connection to the genealogy DB.fetch_one(cursor, sql, params, log) — Execute a query and fetch a single row.fetch_all(cursor, sql, params, log) — Execute a query and fetch all rows.api.genealogy.persons_support
This module contains the helper functions used specifically by
persons.py, moved out of the endpoint module to keep it thin:
generate_columns_str(columns)getFamilies(cursor, person, log)getReadAlso(person, cursor, log)getFamObj(fam_key, cursor)Example (simplified):
from api.genealogy.support import fetch_one, fetch_all
def generate_columns_str(columns):
parts = []
for col in columns:
if "expression" in col:
expr = col["expression"]
alias = col.get("alias")
parts.append(f"{expr} AS `{alias}`" if alias else expr)
else:
field = f"`{col['field']}`"
alias = col.get("alias")
parts.append(f"{field} AS `{alias}`" if alias else field)
return ", ".join(parts)
# ... getFamilies, getReadAlso, getFamObj ...
These helpers encapsulate the SQL logic and result shaping, while the endpoint focuses on HTTP and JSON.
To add a new domain, for example api.example:
api/example/__init__.py with a Blueprint and a
register_api(app) function following the same pattern as
api.genealogy.__init__.py.
api/example that implement
register_api(bp).
run.py will automatically discover api.example and
call register_api(app); no changes to
run.py are required.
To add a new endpoint to an existing domain, for example
/genealogy/status:
api/genealogy/status.py:
from flask import Blueprint, jsonify
def register_api(bp: Blueprint):
@bp.route("/status", methods=["GET"])
def status():
return jsonify({"domain": "genealogy", "status": "ok"})
The new route will:
/genealogy/status.registered_apis.json under the proper module./docs OpenAPI-like schema.