Skip to content

Commit

Permalink
v2.14.4
Browse files Browse the repository at this point in the history
Merge pull request #1613 from AntaresSimulatorTeam/hotfix/v2.14.4
laurent-laporte-pro authored Jun 28, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents 4de8f93 + e9ec1b5 commit ef52bce
Showing 41 changed files with 552 additions and 228 deletions.
4 changes: 2 additions & 2 deletions antarest/__init__.py
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@

# Standard project metadata

__version__ = "2.14.3"
__version__ = "2.14.4"
__author__ = "RTE, Antares Web Team"
__date__ = "2023-06-20"
__date__ = "2023-06-28"
# noinspection SpellCheckingInspection
__credits__ = "(c) Réseau de Transport de l’Électricité (RTE)"

6 changes: 4 additions & 2 deletions antarest/core/core_blueprint.py
Original file line number Diff line number Diff line change
@@ -63,7 +63,9 @@ def kill_worker(
) -> Any:
if not current_user.is_site_admin():
raise UserHasNotPermissionError()
logging.getLogger(__name__).warning("Killing the worker")
exit(1)
logging.getLogger(__name__).critical("Killing the worker")
# PyInstaller modifies the behavior of built-in functions, such as `exit`.
# It is advisable to use `sys.exit` or raise the `SystemExit` exception instead.
raise SystemExit(f"Worker killed by the user #{current_user.id}")

return bp
19 changes: 13 additions & 6 deletions antarest/core/version_info.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""
Python module that is dedicated to printing application version and dependencies information
"""
import os
import subprocess
from pathlib import Path
from typing import Dict
import sys

from pydantic import BaseModel

@@ -57,11 +59,9 @@ def get_commit_id(resources_dir: Path) -> str:

def get_last_commit_from_git() -> str:
"""Returns the commit ID of the current Git HEAD, or ""."""
command = "git log -1 HEAD --format=%H"
command = ["git", "log", "-1", "HEAD", "--format=%H"]
try:
return subprocess.check_output(
command, encoding="utf-8", shell=True
).strip()
return subprocess.check_output(command, encoding="utf-8").strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return ""

@@ -79,8 +79,16 @@ def get_dependencies() -> Dict[str, str]:
subprocess.CalledProcessError:
If the `pip freeze` command fails for some reason.
"""
python_name = Path(sys.executable).with_suffix("").name
if python_name.lower() != "python":
# Due to PyInstaller renaming the executable to "AntaresWebServer",
# accessing the "python" executable becomes impossible, resulting in complications
# when trying to obtain the list of installed packages using `pip freeze`.
return {}

# fmt: off
output = subprocess.check_output("pip freeze", encoding="utf-8", shell=True)
args = [sys.executable, "-m", "pip", "freeze"]
output = subprocess.check_output(args, encoding="utf-8")
lines = (
line
for line in output.splitlines(keepends=False)
@@ -90,4 +98,3 @@ def get_dependencies() -> Dict[str, str]:
packages = dict(line.split("==", 1) for line in lines)
# AntaREST is not a dependency of AntaREST
return {k: v for k, v in packages.items() if k.lower() != "antarest"}
# fmt: on
9 changes: 7 additions & 2 deletions antarest/launcher/adapters/local_launcher/local_launcher.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
import threading
import time
from pathlib import Path
from typing import IO, Callable, Dict, Optional, Tuple, cast
from typing import IO, Callable, Dict, Optional, Tuple, cast, List
from uuid import UUID

from antarest.core.config import Config
@@ -118,8 +118,13 @@ def stop_reading_output() -> bool:
str(uuid), study_uuid, export_path, launcher_parameters
)

args = [
str(antares_solver_path),
f"--force-parallel={launcher_parameters.nb_cpu}",
str(export_path),
]
process = subprocess.Popen(
[antares_solver_path, export_path],
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
96 changes: 73 additions & 23 deletions antarest/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import argparse
import copy
import logging
import re
from pathlib import Path
from typing import Any, Dict, Optional, Tuple, cast
from typing import Any, Dict, Optional, Sequence, Tuple, cast

import pydantic
import sqlalchemy.ext.baked # type: ignore
@@ -32,11 +33,17 @@
from ratelimit import RateLimitMiddleware # type: ignore
from ratelimit.backends.redis import RedisBackend # type: ignore
from ratelimit.backends.simple import MemoryBackend # type: ignore
from starlette.middleware.base import (
BaseHTTPMiddleware,
DispatchFunction,
RequestResponseEndpoint,
)
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from starlette.types import ASGIApp

logger = logging.getLogger(__name__)

@@ -174,6 +181,59 @@ def parse_arguments() -> argparse.Namespace:
return parser.parse_args()


class URLRewriterMiddleware(BaseHTTPMiddleware):
"""
Middleware that rewrites the URL path to "/" (root path) for incoming requests
that do not match the known end points. This is useful for redirecting requests
to the main page of a ReactJS application when the user refreshes the browser.
"""

def __init__(
self,
app: ASGIApp,
dispatch: Optional[DispatchFunction] = None,
root_path: str = "",
route_paths: Sequence[str] = (),
) -> None:
"""
Initializes an instance of the URLRewriterMiddleware.
Args:
app: The ASGI application to which the middleware is applied.
dispatch: The dispatch function to use.
root_path: The root path of the application.
The URL path will be rewritten relative to this root path.
route_paths: The known route paths of the application.
Requests that do not match any of these paths will be rewritten to the root path.
Note:
The `root_path` can be set to a specific component of the URL path, such as "api".
The `route_paths` should contain all the known endpoints of the application.
"""
dispatch = self.dispatch if dispatch is None else dispatch
super().__init__(app, dispatch)
self.root_path = f"/{root_path}" if root_path else ""
self.known_prefixes = {
re.findall(r"/(?:(?!/).)*", p)[0] for p in route_paths if p != "/"
}

async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Any:
"""
Intercepts the incoming request and rewrites the URL path if necessary.
Passes the modified or original request to the next middleware or endpoint handler.
"""
url_path = request.scope["path"]
if url_path in {"", "/"}:
pass
elif self.root_path and url_path.startswith(self.root_path):
request.scope["path"] = url_path[len(self.root_path) :]
elif not any(url_path.startswith(ep) for ep in self.known_prefixes):
request.scope["path"] = "/"
return await call_next(request)


def fastapi_app(
config_file: Path,
resource_path: Optional[Path] = None,
@@ -209,17 +269,6 @@ def fastapi_app(

@application.get("/", include_in_schema=False)
def home(request: Request) -> Any:
"""
Home ui
---
responses:
'200':
content:
application/html: {}
description: html home page
tags:
- UI
"""
return templates.TemplateResponse(
"index.html", {"request": request}
)
@@ -228,17 +277,6 @@ def home(request: Request) -> Any:
# noinspection PyUnusedLocal
@application.get("/", include_in_schema=False)
def home(request: Request) -> Any:
"""
Home ui
---
responses:
'200':
content:
application/html: {}
description: html home page
tags:
- UI
"""
return ""

@application.on_event("startup")
@@ -386,6 +424,18 @@ def handle_all_exception(request: Request, exc: Exception) -> Any:

services = create_services(config, application)

if mount_front:
# When the web application is running in Desktop mode, the ReactJS web app
# is served at the `/static` entry point. Any requests that are not API
# requests should be redirected to the `index.html` file, which will handle
# the route provided by the URL.
route_paths = [r.path for r in application.routes] # type: ignore
application.add_middleware(
URLRewriterMiddleware,
root_path=application.root_path,
route_paths=route_paths,
)

if (
config.server.services
and Module.WATCHER.value in config.server.services
84 changes: 62 additions & 22 deletions antarest/matrixstore/model.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import datetime
import uuid
from datetime import datetime
from typing import Any, List, Optional, Union

from pydantic import BaseModel
from sqlalchemy import Column, String, Enum, DateTime, Table, ForeignKey, Integer, Boolean # type: ignore
from sqlalchemy.orm import relationship # type: ignore
from sqlalchemy.orm.collections import attribute_mapped_collection # type: ignore
from typing import Any, List, Union

from antarest.core.persistence import Base
from antarest.login.model import GroupDTO, Identity, UserInfo
from pydantic import BaseModel
from sqlalchemy import ( # type: ignore
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
String,
Table,
)
from sqlalchemy.orm import relationship # type: ignore


class Matrix(Base): # type: ignore
"""
Represents a matrix object in the database.
Attributes:
id: A SHA256 hash for the matrix data (primary key).
width: Number of columns in the matrix.
height: Number of rows in the matrix.
created_at: Creation date of the matrix (unknown usage).
"""

# noinspection SpellCheckingInspection
__tablename__ = "matrix"

id = Column(String(64), primary_key=True)
width = Column(Integer)
height = Column(Integer)
created_at = Column(DateTime)
id: str = Column(String(64), primary_key=True)
width: int = Column(Integer)
height: int = Column(Integer)
created_at: datetime.datetime = Column(DateTime)

def __eq__(self, other: Any) -> bool:
if not isinstance(other, Matrix):
@@ -59,19 +76,23 @@ class MatrixDataSetDTO(BaseModel):


class MatrixDataSetRelation(Base): # type: ignore
# noinspection SpellCheckingInspection
__tablename__ = "dataset_matrices"
dataset_id = Column(

# noinspection SpellCheckingInspection
dataset_id: str = Column(
String,
ForeignKey("dataset.id", name="fk_matrixdatasetrelation_dataset_id"),
primary_key=True,
)
matrix_id = Column(
# noinspection SpellCheckingInspection
matrix_id: str = Column(
String,
ForeignKey("matrix.id", name="fk_matrixdatasetrelation_matrix_id"),
primary_key=True,
)
name = Column(String, primary_key=True)
matrix = relationship(Matrix)
name: str = Column(String, primary_key=True)
matrix: Matrix = relationship(Matrix)

def __eq__(self, other: Any) -> bool:
if not isinstance(other, MatrixDataSetRelation):
@@ -87,24 +108,43 @@ def __eq__(self, other: Any) -> bool:


class MatrixDataSet(Base): # type: ignore
"""
Represents a user dataset containing matrices in the database.
Attributes:
id: The unique identifier of the dataset (primary key).
name: The name of the dataset.
owner_id: The foreign key referencing the owner's identity.
public: Indicates whether the dataset is public or not.
created_at: The creation date of the dataset.
updated_at: The last update date of the dataset.
Relationships:
owner (Identity): The relationship to the owner's identity.
groups (List[Group]): The relationship to groups associated with the dataset.
matrices (List[MatrixDataSetRelation]): The relationship to matrix dataset relations.
"""

# noinspection SpellCheckingInspection
__tablename__ = "dataset"

id = Column(
id: str = Column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
unique=True,
)
name = Column(String)
owner_id = Column(
name: str = Column(String)
# noinspection SpellCheckingInspection
owner_id: int = Column(
Integer,
ForeignKey("identities.id", name="fk_matrixdataset_identities_id"),
)
public = Column(Boolean, default=False)
created_at = Column(DateTime)
updated_at = Column(DateTime)
public: bool = Column(Boolean, default=False)
created_at: datetime.datetime = Column(DateTime)
updated_at: datetime.datetime = Column(DateTime)

owner = relationship(Identity)
owner: Identity = relationship(Identity)
groups = relationship(
"Group",
secondary=lambda: groups_dataset_relation,
2 changes: 1 addition & 1 deletion antarest/matrixstore/repository.py
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ def query(
"""
query = db.session.query(MatrixDataSet)
if name is not None:
query = query.filter(MatrixDataSet.name.ilike(f"%{name}%"))
query = query.filter(MatrixDataSet.name.ilike(f"%{name}%")) # type: ignore
if owner is not None:
query = query.filter(MatrixDataSet.owner_id == owner)
datasets: List[MatrixDataSet] = query.distinct().all()
Loading

0 comments on commit ef52bce

Please sign in to comment.