Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Google
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be handled as part of the flask configuration? https://docs.authlib.org/en/latest/client/flask.html#configuration

Authlib Flask OAuth registry can load the configuration from Flask app.config automatically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this file is not used anymore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why is it still here?

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

# GitHub
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

# Microsoft
MICROSOFT_CLIENT_ID=your_microsoft_client_id
MICROSOFT_CLIENT_SECRET=your_microsoft_client_secret

# General
SECRET_KEY=supersecret
BASE_URL=http://localhost:5000
127 changes: 49 additions & 78 deletions gramps_webapi/app.py
Original file line number Diff line number Diff line change
@@ -1,210 +1,181 @@
#
# Gramps Web API - A RESTful API for the Gramps genealogy program
#
# Copyright (C) 2020-2022 David Straub
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot remove the copyright notice!! (I know ChatGPT likes to do that...)

"""Flask web app providing a REST API to a Gramps family tree."""


import logging
import os
import warnings
from typing import Any, Dict, Optional


from flask import Flask, abort, g, send_from_directory
from flask_compress import Compress
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from gramps.gen.config import config as gramps_config
from gramps.gen.config import set as setconfig


from .api import api_blueprint
from .api.cache import request_cache, thumbnail_cache
from .api.cache import thumbnail_cache, request_cache
from .api.ratelimiter import limiter
from .api.search.embeddings import load_model
from .api.util import close_db
from .auth import user_db
from .auth.oidc import configure_oauth, oidc_bp
from .config import DefaultConfig, DefaultConfigJWT
from .const import API_PREFIX, ENV_CONFIG_FILE, TREE_MULTI
from .dbmanager import WebDbManager
from .util.celery import create_celery


def deprecated_config_from_env(app):
"""Add deprecated config from environment variables.

This function will be removed eventually!
"""
"""Add deprecated config from environment variables (legacy support)."""
options = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this reformatted?

"TREE",
"SECRET_KEY",
"USER_DB_URI",
"POSTGRES_USER",
"POSTGRES_PASSWORD",
"MEDIA_BASE_DIR",
"SEARCH_INDEX_DIR",
"EMAIL_HOST",
"EMAIL_PORT",
"EMAIL_HOST_USER",
"EMAIL_HOST_PASSWORD",
"DEFAULT_FROM_EMAIL",
"BASE_URL",
"STATIC_PATH",
"TREE", "SECRET_KEY", "USER_DB_URI", "POSTGRES_USER", "POSTGRES_PASSWORD",
"MEDIA_BASE_DIR", "SEARCH_INDEX_DIR", "EMAIL_HOST", "EMAIL_PORT",
"EMAIL_HOST_USER", "EMAIL_HOST_PASSWORD", "DEFAULT_FROM_EMAIL",
"BASE_URL", "STATIC_PATH",
]
for option in options:
value = os.getenv(option)
if value:
app.config[option] = value
warnings.warn(
f"Setting the `{option}` config option via the `{option}` environment"
" variable is deprecated and will stop working in the future."
f" Please use `GRAMPSWEB_{option}` instead."
f"Setting `{option}` via the environment is deprecated. "
f"Use `GRAMPSWEB_{option}` instead."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change this??

)
return app


def create_app(config: Optional[Dict[str, Any]] = None, config_from_env: bool = True):
"""Flask application factory."""
app = Flask(__name__)

app.logger.setLevel(logging.INFO)

# load default config

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If your LLM removes the comments, you need to put them back in

app.config.from_object(DefaultConfig)

# overwrite with user config file

if os.getenv(ENV_CONFIG_FILE):
app.config.from_envvar(ENV_CONFIG_FILE)

# use unprefixed environment variables if exist - deprecated!

deprecated_config_from_env(app)

# use prefixed environment variables if exist

if config_from_env:
app.config.from_prefixed_env(prefix="GRAMPSWEB")

# update config from dictionary if present

if config:
app.config.update(**config)

# fail if required config option is missing

required_options = ["TREE", "SECRET_KEY", "USER_DB_URI"]
for option in required_options:
if not app.config.get(option):
raise ValueError(f"{option} must be specified")

# environment variable to set the Gramps database path.
# Needed for backwards compatibility from Gramps 6.0 onwards

if db_path := os.getenv("GRAMPS_DATABASE_PATH"):
setconfig("database.path", db_path)


if app.config.get("LOG_LEVEL"):
app.logger.setLevel(app.config["LOG_LEVEL"])


if app.config["TREE"] != TREE_MULTI:
# create database if missing (only in single-tree mode)
WebDbManager(
name=app.config["TREE"],
create_if_missing=True,
ignore_lock=app.config["IGNORE_DB_LOCK"],
)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't add white space changes in unmodified code - it clutters the diff

if app.config["TREE"] == TREE_MULTI and not app.config["MEDIA_PREFIX_TREE"]:
warnings.warn(
"You have enabled multi-tree support, but `MEDIA_PREFIX_TREE` is "
"set to `False`. This is strongly discouraged as it exposes media "
"files to users belonging to different trees!"
)
warnings.warn("Multi-tree mode is enabled but MEDIA_PREFIX_TREE is False.")


if app.config["TREE"] == TREE_MULTI and app.config["NEW_DB_BACKEND"] != "sqlite":
# needed in case a new postgres tree is to be created
gramps_config.set("database.host", app.config["POSTGRES_HOST"])
gramps_config.set("database.port", str(app.config["POSTGRES_PORT"]))

# load JWT default settings
app.config.from_object(DefaultConfigJWT)

# instantiate JWT manager
app.config.from_object(DefaultConfigJWT)
JWTManager(app)


app.config["SQLALCHEMY_DATABASE_URI"] = app.config["USER_DB_URI"]
user_db.init_app(app)


request_cache.init_app(app, config=app.config["REQUEST_CACHE_CONFIG"])


configure_oauth(app)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will register the providers.

Does this involve an HTTP request to the provider endpoint? If so, what if that request fails? Will the whole flask app fail to start?

app.register_blueprint(oidc_bp)


thumbnail_cache.init_app(app, config=app.config["THUMBNAIL_CACHE_CONFIG"])

# enable CORS for /api/... resources

if app.config.get("CORS_ORIGINS"):
CORS(
app,
resources={f"{API_PREFIX}/*": {"origins": app.config["CORS_ORIGINS"]}},
)
CORS(app, resources={f"{API_PREFIX}/*": {"origins": app.config["CORS_ORIGINS"]}})


# enable gzip compression
Compress(app)


static_path = app.config.get("STATIC_PATH")

# routes for static hosting (e.g. SPA frontend)

@app.route("/", methods=["GET", "POST"])
def send_index():
return send_from_directory(static_path, "index.html")


@app.route("/<path:path>", methods=["GET", "POST"])
def send_static(path):
if path.startswith(API_PREFIX[1:]):
# we don't want any erroneous API calls to end up here!
abort(404)
if path and os.path.exists(os.path.join(static_path, path)):
return send_from_directory(static_path, path)
else:
return send_from_directory(static_path, "index.html")
return send_from_directory(static_path, "index.html")


# register the API blueprint
app.register_blueprint(api_blueprint)
limiter.init_app(app)

# instantiate celery
create_celery(app)


@app.teardown_appcontext
def close_db_connection(exception) -> None:
"""Close the Gramps database after every request."""
db = g.pop("db", None)
if db:
close_db(db)
db_write = g.pop("db_write", None)
if db_write:
close_db(db_write)


@app.teardown_request
def close_user_db_connection(exception) -> None:
"""Close the user database after every request."""
if exception:
user_db.session.rollback() # pylint: disable=no-member
user_db.session.close() # pylint: disable=no-member
user_db.session.remove() # pylint: disable=no-member
user_db.session.rollback()
user_db.session.close()
user_db.session.remove()


if app.config.get("VECTOR_EMBEDDING_MODEL"):
app.config["_INITIALIZED_VECTOR_EMBEDDING_MODEL"] = load_model(
app.config["VECTOR_EMBEDDING_MODEL"]
)


@app.route("/ready", methods=["GET"])
def ready():
return {"status": "ready"}, 200


return app
Loading