Skip to content

Commit be6d186

Browse files
Pierre-NarcisiTheoLechemiabouttierjacquesfize
committed
(Dependencies) Upgrade SQLAlchemy to 1.4 and other requirements (flask 3.0), remove Debian 10 and Python 3.7 support (PnX-SI#2751)
* Drop support for Debian 10 and python <3.9 @jacquesfize @Pierre-Narcisi @bouttier * Update to Flask 3.0 * Update to SQLAlchemy 1.4 (query to 2.0 style, fix warnings and tests) * new requirements-dev.txt * Increase test coverage - gn_meta/repositories - gn_meta/mtd - occtax - occhab - utilstoml - install-gn-module commands * (temporary) use of CustomSelect instead of Query --> Station (deleted later) * Change fixtures: datasets + stations + user * Remove deprecated and unused modules (utilsgeometry.py, utilssqlalchemy.py,config_manager.py) --------- Co-authored-by: TheoLechemia <[email protected]> Co-authored-by: Élie Bouttier <[email protected]> Co-authored-by: Jacques Fize <[email protected]> Co-authored-by: Pierre Narcisi <[email protected]>
1 parent 5012c48 commit be6d186

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2450
-2357
lines changed

.github/workflows/pytest.yml

+9-13
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,16 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
debian-version: [ '10', '11', '12' ]
22+
debian-version: ["11", "12"]
2323
include:
24-
- debian-version: '10'
25-
python-version: '3.7'
26-
postgres-version: '11'
27-
postgis-version: '2.5'
28-
- debian-version: '11'
29-
python-version: '3.9'
30-
postgres-version: '13'
31-
postgis-version: '3.2'
32-
- debian-version: '12'
33-
python-version: '3.11'
34-
postgres-version: '15'
35-
postgis-version: '3.3'
24+
- debian-version: "11"
25+
python-version: "3.9"
26+
postgres-version: "13"
27+
postgis-version: "3.2"
28+
- debian-version: "12"
29+
python-version: "3.11"
30+
postgres-version: "15"
31+
postgis-version: "3.3"
3632

3733
name: Debian ${{ matrix.debian-version }}
3834

backend/geonature/app.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from flask_mail import Message
1818
from flask_cors import CORS
1919
from flask_login import current_user
20-
from flask_sqlalchemy import before_models_committed
20+
from flask_sqlalchemy.track_modifications import before_models_committed
2121
from werkzeug.middleware.proxy_fix import ProxyFix
2222
from werkzeug.middleware.shared_data import SharedDataMiddleware
2323
from werkzeug.middleware.dispatcher import DispatcherMiddleware
@@ -86,7 +86,7 @@ class MyJSONProvider(DefaultJSONProvider):
8686
@staticmethod
8787
def default(o):
8888
if isinstance(o, Row):
89-
return dict(o)
89+
return o._asdict()
9090
return DefaultJSONProvider.default(o)
9191

9292

backend/geonature/core/command/create_gn_module.py

+34-15
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1+
import importlib
12
import os
2-
import sys
3-
import subprocess
43
import site
5-
import importlib
4+
import subprocess
5+
import sys
66
from pathlib import Path
77

8+
import pathlib # For testing purposes
89
import click
10+
import geonature.utils.config
911
from click import ClickException
10-
11-
from geonature.utils.env import ROOT_DIR
12-
from geonature.utils.module import iter_modules_dist, get_dist_from_code, module_db_upgrade
13-
1412
from geonature.core.command.main import main
15-
import geonature.utils.config
16-
from geonature.utils.config import config
1713
from geonature.utils.command import (
18-
install_frontend_dependencies,
19-
create_frontend_module_config,
2014
build_frontend,
15+
create_frontend_module_config,
16+
install_frontend_dependencies,
2117
)
18+
from geonature.utils.config import config
19+
from geonature.utils.env import ROOT_DIR
20+
from geonature.utils.module import get_dist_from_code, iter_modules_dist, module_db_upgrade
2221

2322

2423
@main.command()
@@ -30,6 +29,29 @@
3029
@click.option("--build", type=bool, required=False, default=True)
3130
@click.option("--upgrade-db", type=bool, required=False, default=True)
3231
def install_gn_module(x_arg, module_path, module_code, build, upgrade_db):
32+
"""
33+
Command definition to install a GeoNature module
34+
35+
Parameters
36+
----------
37+
x_arg : list
38+
additional arguments
39+
module_path : str
40+
path of the module directory
41+
module_code : str
42+
code of the module, deprecated in future release
43+
build : boolean
44+
is the frontend rebuild
45+
upgrade_db : boolean
46+
migrate the revision associated with the module
47+
48+
Raises
49+
------
50+
ClickException
51+
No module found with the given module code
52+
ClickException
53+
No module code was detected in the code
54+
"""
3355
click.echo("Installation du backend…")
3456
subprocess.run(f"pip install -e '{module_path}'", shell=True, check=True)
3557

@@ -40,7 +62,7 @@ def install_gn_module(x_arg, module_path, module_code, build, upgrade_db):
4062
if module_code:
4163
# load python package
4264
module_dist = get_dist_from_code(module_code)
43-
if not module_dist:
65+
if not module_dist: # FIXME : technically can't go there...
4466
raise ClickException(f"Aucun module ayant pour code {module_code} n’a été trouvé")
4567
else:
4668
for module_dist in iter_modules_dist():
@@ -56,7 +78,6 @@ def install_gn_module(x_arg, module_path, module_code, build, upgrade_db):
5678
raise ClickException(
5779
f"Impossible de détecter le code du module, essayez de le spécifier."
5880
)
59-
6081
# symlink module in exernal module directory
6182
module_frontend_path = (module_path / "frontend").resolve()
6283
module_symlink = ROOT_DIR / "frontend" / "external_modules" / module_code.lower()
@@ -68,7 +89,6 @@ def install_gn_module(x_arg, module_path, module_code, build, upgrade_db):
6889
else:
6990
click.echo(f"Création du lien symbolique {module_symlink}{module_frontend_path}")
7091
os.symlink(module_frontend_path, module_symlink)
71-
7292
if (Path(module_path) / "frontend" / "package-lock.json").is_file():
7393
click.echo("Installation des dépendances frontend…")
7494
install_frontend_dependencies(module_frontend_path)
@@ -80,7 +100,6 @@ def install_gn_module(x_arg, module_path, module_code, build, upgrade_db):
80100
click.echo("Rebuild du frontend …")
81101
build_frontend()
82102
click.secho("Rebuild du frontend terminé.", fg="green")
83-
84103
if upgrade_db:
85104
click.echo("Installation / mise à jour de la base de données…")
86105
if not module_db_upgrade(module_dist, x_arg=x_arg):

backend/geonature/core/errors.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from flask import current_app, request, json, redirect
66
from werkzeug.exceptions import Unauthorized, InternalServerError, HTTPException, BadRequest
7-
from werkzeug.urls import url_encode
7+
from urllib.parse import urlencode
88
from marshmallow.exceptions import ValidationError
99

1010

@@ -32,7 +32,7 @@ def handle_unauthenticated_request(e):
3232
next_url = request.full_path
3333
else:
3434
next_url = request.url
35-
query_string = url_encode({"next": next_url})
35+
query_string = urlencode({"next": next_url})
3636
return redirect(f"{base_url}{login_path}?{query_string}")
3737

3838

backend/geonature/core/gn_commons/admin.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,20 @@ class BibFieldAdmin(CruvedProtectedMixin, ModelView):
7979
"field_order": {"label": "Ordre"},
8080
"additional_attributes": {"label": "Attribut additionnels"},
8181
"modules": {
82-
"query_factory": lambda: DB.session.query(TModules).filter(
83-
TModules.module_code.in_(
84-
current_app.config["ADDITIONAL_FIELDS"]["IMPLEMENTED_MODULES"]
82+
"query_factory": lambda: DB.session.scalars(
83+
DB.select(TModules).where(
84+
TModules.module_code.in_(
85+
current_app.config["ADDITIONAL_FIELDS"]["IMPLEMENTED_MODULES"]
86+
)
8587
)
8688
)
8789
},
8890
"objects": {
89-
"query_factory": lambda: DB.session.query(PermObject).filter(
90-
PermObject.code_object.in_(
91-
current_app.config["ADDITIONAL_FIELDS"]["IMPLEMENTED_OBJECTS"]
91+
"query_factory": lambda: DB.session.scalars(
92+
DB.select(PermObject).where(
93+
PermObject.code_object.in_(
94+
current_app.config["ADDITIONAL_FIELDS"]["IMPLEMENTED_OBJECTS"]
95+
)
9296
)
9397
)
9498
},

backend/geonature/core/gn_commons/medias/routes.py

+10-16
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,15 @@
22
Route permettant de manipuler les fichiers
33
contenus dans gn_media
44
"""
5-
import json
6-
7-
from flask import Blueprint, request, current_app, redirect, jsonify
5+
from flask import request, redirect, jsonify
86
from werkzeug.exceptions import NotFound
97

10-
from geonature.core.gn_commons.repositories import TMediaRepository, TMediumRepository
8+
from geonature.core.gn_commons.repositories import TMediaRepository
119
from geonature.core.gn_commons.models import TMedias
1210
from geonature.utils.env import DB
1311
from utils_flask_sqla.response import json_resp, json_resp_accept_empty_list
1412

1513

16-
from geonature.utils.errors import (
17-
GeoNatureError,
18-
GeonatureApiError,
19-
)
20-
2114
from ..routes import routes
2215

2316

@@ -29,8 +22,9 @@ def get_medias(uuid_attached_row):
2922
.. :quickref: Commons;
3023
"""
3124

32-
res = DB.session.query(TMedias).filter(TMedias.uuid_attached_row == uuid_attached_row).all()
33-
25+
res = DB.session.scalars(
26+
DB.select(TMedias).filter(TMedias.uuid_attached_row == uuid_attached_row)
27+
).all()
3428
return [r.as_dict() for r in (res or [])]
3529

3630

@@ -41,10 +35,10 @@ def get_media(id_media):
4135
.. :quickref: Commons;
4236
"""
4337

44-
m = TMediaRepository(id_media=id_media).media
45-
if not m:
38+
media = TMediaRepository(id_media=id_media).media
39+
if not media:
4640
raise NotFound
47-
return jsonify(m.as_dict())
41+
return jsonify(media.as_dict())
4842

4943

5044
@routes.route("/media", methods=["POST", "PUT"])
@@ -59,14 +53,14 @@ def insert_or_update_media(id_media=None):
5953
"""
6054

6155
# gestion des parametres de route
62-
56+
# @TODO utilisé quelque part ?
6357
if request.files:
6458
file = request.files["file"]
6559
else:
6660
file = None
6761

6862
data = {}
69-
# Useful ?
63+
# Useful ? @jacquesfize YES ! -> used when add media when adding a taxon occurrence
7064
if request.form:
7165
formData = dict(request.form)
7266
for key in formData:

backend/geonature/core/gn_commons/models/additional_fields.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ class TAdditionalFields(DB.Model):
3737
exportable = DB.Column(DB.Boolean, default=True)
3838
field_order = DB.Column(DB.Integer)
3939
type_widget = DB.relationship("BibWidgets")
40-
bib_nomenclature_type = DB.relationship(
41-
"BibNomenclaturesTypes",
42-
primaryjoin="BibNomenclaturesTypes.mnemonique == TAdditionalFields.code_nomenclature_type",
43-
)
40+
bib_nomenclature_type = DB.relationship("BibNomenclaturesTypes")
4441
additional_attributes = DB.Column(JSONB)
4542
multiselect = DB.Column(DB.Boolean)
4643
api = DB.Column(DB.String)
@@ -50,7 +47,9 @@ class TAdditionalFields(DB.Model):
5047
secondary=cor_field_module,
5148
)
5249
objects = DB.relationship(PermObject, secondary=cor_field_object)
53-
datasets = DB.relationship(TDatasets, secondary=cor_field_dataset)
50+
datasets = DB.relationship(
51+
TDatasets, secondary=cor_field_dataset, back_populates="additional_fields"
52+
)
5453

5554
def __str__(self):
5655
return f"{self.field_label} ({self.description})"

backend/geonature/core/gn_commons/models/base.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class TMedias(DB.Model):
121121
id_table_location = DB.Column(
122122
DB.Integer, ForeignKey("gn_commons.bib_tables_location.id_table_location")
123123
)
124-
unique_id_media = DB.Column(UUID(as_uuid=True), default=select([func.uuid_generate_v4()]))
124+
unique_id_media = DB.Column(UUID(as_uuid=True), default=select(func.uuid_generate_v4()))
125125
uuid_attached_row = DB.Column(UUID(as_uuid=True))
126126
title_fr = DB.Column(DB.Unicode)
127127
title_en = DB.Column(DB.Unicode)
@@ -206,19 +206,24 @@ class TValidations(DB.Model):
206206
nomenclature_valid_status = relationship(
207207
TNomenclatures,
208208
foreign_keys=[id_nomenclature_valid_status],
209-
lazy="joined",
209+
lazy="joined", # FIXME: remove and manually join when needed
210210
)
211211
id_validator = DB.Column(DB.Integer, ForeignKey(User.id_role))
212212
validator_role = DB.relationship(User)
213213
validation_auto = DB.Column(DB.Boolean)
214214
validation_comment = DB.Column(DB.Unicode)
215215
validation_date = DB.Column(DB.TIMESTAMP)
216216
validation_auto = DB.Column(DB.Boolean)
217-
validation_label = DB.relationship(TNomenclatures)
217+
# FIXME: remove and use nomenclature_valid_status
218+
validation_label = DB.relationship(
219+
TNomenclatures,
220+
foreign_keys=[id_nomenclature_valid_status],
221+
overlaps="nomenclature_valid_status", # overlaps expected
222+
)
218223

219224

220225
last_validation_query = (
221-
select([TValidations])
226+
select(TValidations)
222227
.order_by(TValidations.validation_date.desc())
223228
.limit(1)
224229
.alias("last_validation")

0 commit comments

Comments
 (0)