From da6f9ddb6c6b1038a986df4643d4412bf9d48163 Mon Sep 17 00:00:00 2001 From: Amandine Date: Tue, 6 Aug 2024 16:45:02 +0200 Subject: [PATCH] Config in toml file (#517) Configuration is now moved in `config/taxhub_config.toml`. Include new parameter `API_PREFIX` for retro-compatibility for GeoNature-Atlas and Occtax-mobile. Include a marshmallow validation schema. --------- Co-authored-by: Jacques Fize <4259846+jacquesfize@users.noreply.github.com> --- .github/workflows/pytest.yml | 7 +-- .gitignore | 2 +- apptax/__init__.py | 15 +++--- apptax/app.py | 22 ++++++--- apptax/utils/config/__init__.py | 0 apptax/utils/config/config_schema.py | 40 ++++++++++++++++ apptax/utils/config/utilstoml.py | 48 +++++++++++++++++++ config/taxhub_config.toml.sample | 14 ++++++ .../test_config.py => config/test_config.toml | 3 -- docs/changelog.md | 2 +- requirements-common.in | 3 +- requirements.txt | 4 +- 12 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 apptax/utils/config/__init__.py create mode 100644 apptax/utils/config/config_schema.py create mode 100644 apptax/utils/config/utilstoml.py create mode 100644 config/taxhub_config.toml.sample rename apptax/test_config.py => config/test_config.toml (76%) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 2c4c028be..82d0902e1 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -11,6 +11,7 @@ on: - master - hotfixes - develop + - flask-admin jobs: build: @@ -89,17 +90,17 @@ jobs: flask db autoupgrade flask db status env: - TAXHUB_SETTINGS: test_config.py + TAXHUB_CONFIG_FILE: config/test_config.toml - name: Install taxref run: | flask taxref import-v17 env: - TAXHUB_SETTINGS: test_config.py + TAXHUB_CONFIG_FILE: config/test_config.toml - name: Test with pytest run: | pytest -v --cov --cov-report xml env: - TAXHUB_SETTINGS: test_config.py + TAXHUB_CONFIG_FILE: config/test_config.toml - name: Upload coverage to Codecov if: ${{ matrix.name == '11' && matrix.sqlalchemy-version == '1.4' }} uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore index 24a5d4c99..97651fa8f 100644 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,4 @@ target/ /docs/changelog.html -/apptax/*_config.py +config/*_config.toml \ No newline at end of file diff --git a/apptax/__init__.py b/apptax/__init__.py index d51b47064..04c604637 100644 --- a/apptax/__init__.py +++ b/apptax/__init__.py @@ -1,9 +1,8 @@ -taxhub_routes = [ - ("apptax.taxonomie.routesbibnoms:adresses", "/api/bibnoms"), - ("apptax.taxonomie.routestaxref:adresses", "/api/taxref"), - ("apptax.taxonomie.routesbibattributs:adresses", "/api/bibattributs"), - ("apptax.taxonomie.routesbiblistes:adresses", "/api/biblistes"), - ("apptax.taxonomie.routestmedias:adresses", "/api/tmedias"), - ("apptax.taxonomie.routesbdcstatuts:adresses", "/api/bdc_statuts"), - ("apptax.admin.admin:adresses", "/"), +taxhub_api_routes = [ + ("apptax.taxonomie.routesbibnoms:adresses", "/bibnoms"), + ("apptax.taxonomie.routestaxref:adresses", "/taxref"), + ("apptax.taxonomie.routesbibattributs:adresses", "/bibattributs"), + ("apptax.taxonomie.routesbiblistes:adresses", "/biblistes"), + ("apptax.taxonomie.routestmedias:adresses", "/tmedias"), + ("apptax.taxonomie.routesbdcstatuts:adresses", "/bdc_statuts"), ] diff --git a/apptax/app.py b/apptax/app.py index 91c8349ec..b6c797f9c 100644 --- a/apptax/app.py +++ b/apptax/app.py @@ -16,6 +16,8 @@ from pypnusershub.login_manager import login_manager from apptax.admin.admin import taxhub_admin, taxhub_admin_addview +from apptax.utils.config.utilstoml import load_and_validate_toml +from apptax.utils.config.config_schema import TaxhubSchemaConf migrate = Migrate() @@ -43,7 +45,10 @@ def configure_alembic(alembic_config): def create_app(): app = Flask(__name__, static_folder=os.environ.get("TAXHUB_STATIC_FOLDER", "static")) - app.config.from_pyfile(os.environ.get("TAXHUB_SETTINGS", "config.py")) + DEFAULT_CONFIG_FILE = Path(__file__).absolute().parent.parent / "config/taxhub_config.toml" + CONFIG_FILE = os.environ.get("TAXHUB_CONFIG_FILE", DEFAULT_CONFIG_FILE) + config = load_and_validate_toml(CONFIG_FILE, TaxhubSchemaConf) + app.config.update(config) app.config.from_prefixed_env(prefix="TAXHUB") media_path = Path(app.config["MEDIA_FOLDER"], "taxhub").absolute() @@ -66,9 +71,6 @@ def create_app(): app.config["DB"] = db - if "CODE_APPLICATION" not in app.config: - app.config["CODE_APPLICATION"] = "TH" - @app.before_request def load_current_user(): g.current_user = current_user if current_user.is_authenticated else None @@ -83,6 +85,7 @@ def favicon(): mimetype="image/vnd.microsoft.icon", ) + # UserHub from pypnusershub import routes app.register_blueprint(routes.routes, url_prefix="/api/auth") @@ -90,13 +93,18 @@ def favicon(): # Flask admin taxhub_admin.init_app(app) taxhub_admin_addview(app, taxhub_admin) + from apptax.admin.admin import adresses + + app.register_blueprint(adresses, url_prefix="/") # API - from apptax import taxhub_routes + from apptax import taxhub_api_routes + + base_api_prefix = app.config["API"].get("API_PREFIX") - for blueprint_path, url_prefix in taxhub_routes: + for blueprint_path, url_prefix in taxhub_api_routes: module_name, blueprint_name = blueprint_path.split(":") blueprint = getattr(import_module(module_name), blueprint_name) - app.register_blueprint(blueprint, url_prefix=url_prefix) + app.register_blueprint(blueprint, url_prefix=base_api_prefix + url_prefix) return app diff --git a/apptax/utils/config/__init__.py b/apptax/utils/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apptax/utils/config/config_schema.py b/apptax/utils/config/config_schema.py new file mode 100644 index 000000000..a0451e0d5 --- /dev/null +++ b/apptax/utils/config/config_schema.py @@ -0,0 +1,40 @@ +""" + Description des options de configuration +""" + +from marshmallow import Schema, fields, validates_schema, ValidationError, post_load, pre_load +from marshmallow.validate import OneOf, Regexp, Email, Length + + +class TaxhubApiConf(Schema): + API_PREFIX = fields.String( + load_default="", + validate=Regexp( + r"(^\/(.+)$)|(^\s*$)", + error="API_PREFIX must start with a slash.", + ), + ) + + +class TaxhubSchemaConf(Schema): + SQLALCHEMY_DATABASE_URI = fields.String( + required=True, + validate=Regexp( + r"^(postgres(?:ql)?)((\+psycopg2)?):\/\/(?:([^@\s]+)@)?([^\/\s]+)(?:\/(\w+))?(?:\?(.+))?", + error="PostgreSQL database URL is invalid. Check for authorized URL here : https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS", + ), + ) + SQLALCHEMY_TRACK_MODIFICATIONS = fields.Boolean(load_default=True) + SESSION_TYPE = fields.String(load_default="filesystem") + SECRET_KEY = fields.String(required=True, validate=Length(min=20)) + CODE_APPLICATION = fields.String(load_default="TH") + # le cookie expire toute les 7 jours par défaut + COOKIE_EXPIRATION = fields.Integer(load_default=3600 * 24 * 7) + COOKIE_AUTORENEW = fields.Boolean(load_default=True) + TRAP_ALL_EXCEPTIONS = fields.Boolean(load_default=False) + APPLICATION_ROOT = fields.String(load_default="/") + MEDIA_FOLDER = fields.String(load_default="media") + PASS_METHOD = fields.String(load_default="hash") + FLASK_ADMIN_SWATCH = fields.String(load_default="cerulean") + FLASK_ADMIN_FLUID_LAYOUT = fields.Boolean(load_default=True) + API = fields.Nested(TaxhubApiConf, load_default=TaxhubApiConf().load({})) diff --git a/apptax/utils/config/utilstoml.py b/apptax/utils/config/utilstoml.py new file mode 100644 index 000000000..c529245ed --- /dev/null +++ b/apptax/utils/config/utilstoml.py @@ -0,0 +1,48 @@ +from pathlib import Path + +import toml +from marshmallow import EXCLUDE +from marshmallow.exceptions import ValidationError + + +class ConfigError(Exception): + """ + Configuration error class + Quand un fichier de configuration n'est pas conforme aux attentes + """ + + def __init__(self, file, value): + self.value = value + self.file = file + + def __str__(self): + msg = "Error in the config file '{}'. Fix the following:\n" + msg = msg.format(self.file) + for key, errors in self.value.items(): + msg += "\n\t{}:\n\t\t- {}".format(key, errors) + return msg + + +def load_and_validate_toml(toml_file, config_schema, partial=None): + """ + Fonction qui charge un fichier toml + et le valide avec un Schema marshmallow + """ + if toml_file: + toml_config = load_toml(toml_file) + else: + toml_config = {} + try: + configs_py = config_schema().load(toml_config, unknown=EXCLUDE, partial=partial) + except ValidationError as e: + raise ConfigError(toml_file, e.messages) + return configs_py + + +def load_toml(toml_file): + """ + Fonction qui charge un fichier toml + """ + if not Path(toml_file).is_file(): + raise Exception("Missing file {}".format(toml_file)) + return toml.load(str(toml_file)) diff --git a/config/taxhub_config.toml.sample b/config/taxhub_config.toml.sample new file mode 100644 index 000000000..b01b22469 --- /dev/null +++ b/config/taxhub_config.toml.sample @@ -0,0 +1,14 @@ + +############################################# +# Taxhub backend global configuration file +############################################# + +# Database +SQLALCHEMY_DATABASE_URI = "postgresql://monuser:monpassachanger@localhost:5432/mabase" + +# Remplacer par une clé alétoire complexe +SECRET_KEY = 'super secret key' + +# Configuration liée à l'api de taxhub +[API] + API_PREFIX = "/api" diff --git a/apptax/test_config.py b/config/test_config.toml similarity index 76% rename from apptax/test_config.py rename to config/test_config.toml index 685da28b2..ad91c3545 100644 --- a/apptax/test_config.py +++ b/config/test_config.toml @@ -2,6 +2,3 @@ SECRET_KEY = 'a7e0a755dd3f2c382bac5b5cea6c9329802aeedf39e3f10d0462ffb52c3f5e99' -COOKIE_EXPIRATION = 3600 - -MEDIA_FOLDER = 'medias' diff --git a/docs/changelog.md b/docs/changelog.md index 059ada194..241fa161a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,7 +7,7 @@ - Changement dans les permissions : seuls les profils 2 et 6 sont utilisés. Il faut un profil 2 pour ajouter des attributs / medias et ajouter des taxons à des listes. Il faut un profil 6 pour pouvoir créer des listes / thêmes / type d'attributs. - Le paramètre `UPLOAD_FOLDER` devient `MEDIA_FOLDER`. Veillez à le remplacer dans le fichier `config.py`. Si vous utilisez TaxHub avec GeoNature, ce paramètre existe déjà et est par défaut à `/backend/medias`. - +- La configuration est maintenant gérée dans le fichier `config/taxhub_config.toml` (#517) 1.14.1 (2024-05-23) =================== diff --git a/requirements-common.in b/requirements-common.in index bed1c5a13..a873e7b54 100644 --- a/requirements-common.in +++ b/requirements-common.in @@ -11,4 +11,5 @@ psycopg2 python-dotenv Pillow<10.0.0 urllib3 -click>=8.1.3 \ No newline at end of file +click>=8.1.3 +toml \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5460f96a0..e9680eae9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -173,8 +173,8 @@ werkzeug==3.0.3 zipp==3.18.2 # via importlib-metadata - - Flask-Admin==1.6.0 WTForms==3.0.1 # via flask-admin + +toml==0.10.2 \ No newline at end of file