Skip to content

Commit

Permalink
contrib: Add main, logger and conf to the input_converter (#62)
Browse files Browse the repository at this point in the history
* deploy the structure of the code

* test sort

* isort fix

* ruff

* mypy + ruff + isort

* dependancy issue

* fix isort

* re

* last one with requirements

* requirements fix

* sorting issue

* there is change in antarescraft

* isort again

* blakc now
  • Loading branch information
killian-scalian authored Jan 24, 2025
1 parent c518cf3 commit f5602fc
Show file tree
Hide file tree
Showing 13 changed files with 642 additions and 29 deletions.
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ annotated-types==0.7.0
# via
# -r requirements.txt
# pydantic
antares-craft==0.1.4
antares-craft==0.1.8rc1
# via -r requirements.txt
antlr4-python3-runtime==4.13.1
# via -r requirements.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ scipy==1.10.1
antlr4-python3-runtime==4.13.1
PyYAML~=6.0.1
pydantic
antares_craft>0.1.3
antares_craft==0.1.8rc1
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ absl-py==2.1.0
# via ortools
annotated-types==0.7.0
# via pydantic
antares-craft==0.1.4
antares-craft==0.1.8rc1
# via -r requirements.in
antlr4-python3-runtime==4.13.1
# via -r requirements.in
Expand Down
6 changes: 3 additions & 3 deletions src/andromede/input_converter/data/config.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[study]
input_path =
output_name =
output_path =
study_path =
output_path =
logger_path =
36 changes: 30 additions & 6 deletions src/andromede/input_converter/src/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,35 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
import logging
from pathlib import Path
from typing import Union
from typing import Optional, Union

from antares.craft.model.area import Area
from antares.craft.model.study import Study, read_study_local
from pandas import DataFrame

from andromede.input_converter.src.utils import resolve_path
from andromede.input_converter.src.utils import resolve_path, transform_to_yaml
from andromede.study.parsing import (
InputComponent,
InputComponentParameter,
InputStudy,
InputPortConnections,
InputStudy,
)
from pandas import DataFrame


class AntaresStudyConverter:
def __init__(self, study_input: Union[Path, Study]):
def __init__(
self,
study_input: Union[Path, Study],
logger: logging.Logger,
output_path: Optional[Path] = None,
):
"""
Initialize processor
"""
self.logger = logger

if isinstance(study_input, Study):
self.study = study_input
self.study_path = study_input.service.config.study_path # type: ignore
Expand All @@ -39,6 +47,10 @@ def __init__(self, study_input: Union[Path, Study]):
else:
raise TypeError("Invalid input type")

self.output_path = (
Path(output_path) if output_path else self.study_path / Path("output.yaml")
)

def _check_dataframe_validity(self, df: DataFrame) -> bool:
"""
Check and validate the following conditions:
Expand All @@ -56,6 +68,7 @@ def _convert_area_to_component_list(
self, areas: list[Area]
) -> list[InputComponent]:
components = []
self.logger.info("Converting areas to component list...")
for area in areas:
components.append(
InputComponent(
Expand All @@ -82,6 +95,7 @@ def _convert_renewable_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting renewables to component list...")
for area in areas:
renewables = area.read_renewables()
for renewable in renewables:
Expand Down Expand Up @@ -133,6 +147,7 @@ def _convert_thermal_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting thermals to component list...")
# Add thermal components for each area
for area in areas:
thermals = area.read_thermal_clusters()
Expand Down Expand Up @@ -204,6 +219,7 @@ def _convert_link_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting links to component list...")
# Add links components for each area
links = self.study.read_links()
for link in links:
Expand Down Expand Up @@ -264,6 +280,7 @@ def _convert_wind_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting wind to component list...")
for area in areas:
series_path = (
self.study_path / "input" / "wind" / "series" / f"wind_{area.id}.txt"
Expand Down Expand Up @@ -299,6 +316,7 @@ def _convert_solar_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting solar to component list...")
for area in areas:
series_path = (
self.study_path / "input" / "solar" / "series" / f"solar_{area.id}.txt"
Expand Down Expand Up @@ -335,6 +353,7 @@ def _convert_load_to_component_list(
) -> tuple[list[InputComponent], list[InputPortConnections]]:
components = []
connections = []
self.logger.info("Converting load to component list...")
for area in areas:
series_path = (
self.study_path / "input" / "load" / "series" / f"load_{area.id}.txt"
Expand Down Expand Up @@ -388,11 +407,16 @@ def convert_study_to_input_study(self) -> InputStudy:
list_components.extend(components)
list_connections.extend(connections)

self.logger.info(
"Converting node, components and connections into Input study..."
)
return InputStudy(
nodes=area_components,
components=list_components,
connections=list_connections,
)

def process_all(self) -> None:
raise NotImplementedError
study = self.convert_study_to_input_study()
self.logger.info("Converting input study into yaml file...")
transform_to_yaml(model=study, output_path=self.output_path)
43 changes: 43 additions & 0 deletions src/andromede/input_converter/src/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (c) 2024, RTE (https://www.rte-france.com)
#
# See AUTHORS.txt
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
import logging
import logging.config
import sys
from typing import Optional


def Logger(name: str, file_name: Optional[str]) -> logging.Logger:
formatter = logging.Formatter(
fmt="%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s",
datefmt="%Y/%m/%d %H:%M:%S",
) # %I:%M:%S %p AM|PM format

if file_name and not str(file_name).endswith(".log"):
file_name = f"{file_name}.log"
logging.basicConfig(
filename=file_name,
format="%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s",
datefmt="%Y/%m/%d %H:%M:%S",
filemode="a",
level=logging.INFO,
)

log_obj = logging.getLogger(name)
log_obj.setLevel(logging.DEBUG)

# console printer
screen_handler = logging.StreamHandler(stream=sys.stdout)
screen_handler.setFormatter(formatter)
logging.getLogger().addHandler(screen_handler)

log_obj.info("Logger object created successfully.. ")
return log_obj
174 changes: 174 additions & 0 deletions src/andromede/input_converter/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Copyright (c) 2024, RTE (https://www.rte-france.com)
#
# See AUTHORS.txt
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
import logging
import os
import sys
from argparse import ArgumentParser, ArgumentTypeError, Namespace
from configparser import ConfigParser
from pathlib import Path

from . import __version__
from .converter import AntaresStudyConverter
from .logger import Logger

DEFAULT: dict = {}
LOGGER_PATH: str = os.path.join(os.path.dirname(__file__), "../data/logging.log")


class PathType:
"""file or directory path type for `argparse` parser
The `PathType` class represents a type of argument that can be used
with the `argparse` library.
This class takes three boolean arguments, `exists`, `file_ok`, and `dir_ok`,
which specify whether the path argument must exist, whether it can be a file,
and whether it can be a directory, respectively.
Example Usage::
import argparse
from antarest.main import PathType
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=PathType(file_ok=True, exists=True))
args = parser.parse_args()
print(args.input)
In the above example, `PathType` is used to specify the type of the `--input`
argument for the `argparse` parser. The argument must be an existing file path.
If the given path is not an existing file, the argparse library raises an error.
The Path object representing the given path is then printed to the console.
"""

def __init__(
self,
exists: bool = False,
file_ok: bool = False,
dir_ok: bool = False,
) -> None:
if not (file_ok or dir_ok):
msg = "Either `file_ok` or `dir_ok` must be set at a minimum."
raise ValueError(msg)
self.exists = exists
self.file_ok = file_ok
self.dir_ok = dir_ok

def __call__(self, string: str) -> Path:
"""
Check whether the given string represents a valid path.
If `exists` is `False`, the method simply returns the given path.
If `exists` is True, it checks whether the path exists and whether it is
a file or a directory, depending on the values of `file_ok` and `dir_ok`.
If the path exists and is of the correct type, the method returns the path;
otherwise, it raises an :class:`argparse.ArgumentTypeError` with an
appropriate error message.
Args:
string: file or directory path
Returns:
the file or directory path
Raises
argparse.ArgumentTypeError: if the path is invalid
"""
file_path = Path(string).expanduser()
if not self.exists:
return file_path
if self.file_ok and self.dir_ok:
if file_path.exists():
return file_path
msg = f"The file or directory path does not exist: '{file_path}'"
raise ArgumentTypeError(msg)
elif self.file_ok:
if file_path.is_file():
return file_path
elif file_path.exists():
msg = f"The path is not a regular file: '{file_path}'"
else:
msg = f"The file path does not exist: '{file_path}'"
raise ArgumentTypeError(msg)
elif self.dir_ok:
if file_path.is_dir():
return file_path
elif file_path.exists():
msg = f"The path is not a directory: '{file_path}'"
else:
msg = f"The directory path does not exist: '{file_path}'"
raise ArgumentTypeError(msg)
else: # pragma: no cover
raise NotImplementedError((self.file_ok, self.dir_ok))


def parse_commandline() -> Namespace:
"""Parse command-line arguments using argparse to specify configuration file paths,
logging options, and version display. Returns the parsed arguments."""
parser = ArgumentParser()
parser.add_argument(
"-c",
"--conf",
type=PathType(exists=True, file_ok=True),
help="Give the path of the configuration file",
default="../data/config.ini",
)
parser.add_argument(
"-l",
"--logging",
type=PathType(exists=True, file_ok=True),
help="Give the path of the logger file",
default=LOGGER_PATH,
)
parser.add_argument(
"-i",
"--study_path",
type=PathType(exists=True, dir_ok=True),
help="Give the path of the study_path",
)
parser.add_argument(
"-o",
"--output_path",
type=PathType(exists=True, dir_ok=True),
help="Give the path of the output path",
)
return parser.parse_args()


if __name__ == "__main__":
config: dict = {}
args = parse_commandline()
logger: logging.Logger = Logger(__name__, args.logging)
config_parser = ConfigParser()

# Load the default configuration dictionary into the config parser.
config_parser.read_dict(DEFAULT)

if args.conf:
# Check if the specified config file exists, if not, exit with an error message.
if not os.path.exists(args.conf):
sys.exit(f"Aborting: missing config file at {args.conf}")
else:
config_parser.read(args.conf)

if not config_parser.has_section("study"):
config_parser.add_section("study")

if args.study_path:
config_parser.set("study", "study_path", str(args.study_path))
if args.output_path:
config_parser.set("study", "output_path", str(args.output_path))

converter = AntaresStudyConverter(
Path(config_parser["study"].get("study_path")), logger=logger
)
converter.process_all()
11 changes: 11 additions & 0 deletions src/andromede/input_converter/src/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Copyright (c) 2024, RTE (https://www.rte-france.com)
#
# See AUTHORS.txt
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
from pathlib import Path

import yaml
Expand Down
Empty file.
Loading

0 comments on commit f5602fc

Please sign in to comment.