Skip to content

Commit

Permalink
Merge branch 'main' into feat/read_links
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle authored Jan 8, 2025
2 parents aec9a4d + 622c193 commit 392c853
Show file tree
Hide file tree
Showing 20 changed files with 633 additions and 34 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m pip install tox
- name: Build README
run: tox -e build-readme

- name: Build Python 🐍 packages
run: python -m build

- name: Publish distribution 📦 to PyPI
# Upload packages only on a tagged commit
if: startsWith(github.ref, 'refs/tags')
Expand Down
21 changes: 21 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
v0.1.7 (2025-01-08)
-------------------

- move doc generation from ci.yml to publish.yml

v0.1.6 (2025-01-08)
-------------------

- Fix concatenate CONCATENATED_README.md files for single Readme at pypi.org

v0.1.5 (2025-01-08)
-------------------

- Concatenate .md files for single Readme at pypi.org

v0.1.4 (2025-01-07)
-------------------

- Allow read_areas method to read area parameters and ui
- Add output functionalities (get_matrix, aggregate_values)

v0.1.3 (2024-12-19)
-------------------

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"

[project]
name = "antares_craft"
version = "0.1.3"
version = "0.1.7"
description = """Antares Craft python library under construction. It will allow to create, update and read antares studies."""
readme = "README.md"
readme = "CONCATENATED_README.md"
license = {file = "LICENSE"}
authors = [
{name="Sylvain Leclerc"},
Expand Down
47 changes: 47 additions & 0 deletions scripts/concat_readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path
import re

project_root = Path(__file__).resolve().parent.parent


files = [
project_root / "README.md",
project_root / "docs/usage.md",
project_root / "docs/developer.md",
project_root / "docs/CHANGELOG.md"
]

output_file = project_root / "CONCATENATED_README.md"

#Function to delete Table of Contents in CONCATENATED_README.md
def remove_table_of_contents(content_text: str) -> str:
lines = content_text.splitlines()
filtered_lines = []

in_table_of_contents = False

for line in lines:
# Remove the Table of Contents title (e.g., **Table of Contents**)
if "**Table of Contents**" in line:
in_table_of_contents = True
continue

# Remove items in Table of Contents
if line.strip().startswith("- ["):
continue

if in_table_of_contents and not line.strip().startswith("-"):
in_table_of_contents = False

if not in_table_of_contents:
filtered_lines.append(line)

return "\n".join(filtered_lines)

with open(output_file, "w", encoding="utf-8") as outfile:
for file in files:
with open(file, "r", encoding="utf-8") as infile:
content = infile.read()
if file.name == "README.md":
content = remove_table_of_contents(content) # Remove Table of Contents
outfile.write(content + "\n\n") # Add spacing between files
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sonar.projectVersion=0.1.3
sonar.projectVersion=0.1.4
sonar.organization=antaressimulatorteam
sonar.projectKey=AntaresSimulatorTeam_antares_craft
sonar.sources=src
Expand Down
14 changes: 14 additions & 0 deletions src/antares/craft/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,17 @@ class OutputsRetrievalError(Exception):
def __init__(self, study_id: str, message: str) -> None:
self.message = f"Could not get outputs for {study_id}: " + message
super().__init__(self.message)


class ConstraintRetrievalError(Exception):
def __init__(self, study_id: str, message: str) -> None:
self.message = f"Could not get binding constraints for {study_id}: " + message
super().__init__(self.message)


class AggregateCreationError(Exception):
def __init__(self, study_id: str, output_id: str, mc_type: str, object_type: str, message: str) -> None:
self.message = (
f"Could not create {mc_type}/{object_type} aggregate for study {study_id}, output {output_id}: " + message
)
super().__init__(self.message)
197 changes: 194 additions & 3 deletions src/antares/craft/model/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,200 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
from enum import Enum
from typing import Optional, Union

import pandas as pd

from pydantic import BaseModel


class Output(BaseModel):
name: str
archived: bool
class MCIndAreas(Enum):
VALUES = "values"
DETAILS = "details"
DETAILS_ST_STORAGE = "details-STstorage"
DETAILS_RES = "details-res"


class MCAllAreas(Enum):
VALUES = "values"
DETAILS = "details"
DETAILS_ST_STORAGE = "details-STstorage"
DETAILS_RES = "details-res"
ID = "id"


class MCIndLinks(Enum):
VALUES = "values"


class MCAllLinks(Enum):
VALUES = "values"
ID = "id"


class Frequency(Enum):
HOURLY = "hourly"
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
ANNUAL = "annual"


class AggregationEntry(BaseModel):
"""
Represents an entry for aggregation queries
Attributes:
frequency: "hourly", "daily", "weekly", "monthly", "annual"
mc_years: Monte Carlo years to include in the query. If left empty, all years are included.
type_ids: which links/areas to be selected (ex: "be - fr"). If empty, all are selected
columns_names: names or regexes (if query_file is of type details) to select columns
"""

query_file: Union[MCAllAreas, MCIndAreas, MCAllLinks, MCIndLinks]
frequency: Frequency
mc_years: Optional[list[str]] = None
type_ids: Optional[list[str]] = None
columns_names: Optional[list[str]] = None

def to_api_query(self, object_type: str) -> str:
mc_years = f"&mc_years={','.join(self.mc_years)}" if self.mc_years else ""
type_ids = f"&{object_type}_ids={','.join(self.type_ids)}" if self.type_ids else ""
columns_names = f"&columns_names={','.join(self.columns_names)}" if self.columns_names else ""

return f"query_file={self.query_file.value}&frequency={self.frequency.value}{mc_years}{type_ids}{columns_names}&format=csv"


class Output:
def __init__(self, name: str, archived: bool, output_service): # type: ignore
self._name = name
self._archived = archived
self._output_service = output_service

@property
def name(self) -> str:
return self._name

@property
def archived(self) -> bool:
return self._archived

def get_matrix(self, path: str) -> pd.DataFrame:
"""
Gets the matrix of the output
Args:
path: output path, eg: "mc-all/areas/south/values-hourly"
Returns: Pandas DataFrame
"""
return self._output_service.get_matrix(self.name, path)

def aggregate_areas_mc_ind(
self,
query_file: str,
frequency: str,
mc_years: Optional[list[str]] = None,
areas_ids: Optional[list[str]] = None,
columns_names: Optional[list[str]] = None,
) -> pd.DataFrame:
"""
Creates a matrix of aggregated raw data for areas with mc-ind
Args:
query_file: values from McIndAreas
frequency: values from Frequency
Returns: Pandas DataFrame corresponding to the aggregated raw data
"""
aggregation_entry = AggregationEntry(
query_file=MCIndAreas(query_file),
frequency=Frequency(frequency),
mc_years=mc_years,
type_ids=areas_ids,
columns_names=columns_names,
)

return self._output_service.aggregate_values(self.name, aggregation_entry, "areas", "ind")

def aggregate_links_mc_ind(
self,
query_file: str,
frequency: str,
mc_years: Optional[list[str]] = None,
areas_ids: Optional[list[str]] = None,
columns_names: Optional[list[str]] = None,
) -> pd.DataFrame:
"""
Creates a matrix of aggregated raw data for links with mc-ind
Args:
query_file: values from McIndLinks
frequency: values from Frequency
Returns: Pandas DataFrame corresponding to the aggregated raw data
"""
aggregation_entry = AggregationEntry(
query_file=MCIndLinks(query_file),
frequency=Frequency(frequency),
mc_years=mc_years,
type_ids=areas_ids,
columns_names=columns_names,
)

return self._output_service.aggregate_values(self.name, aggregation_entry, "links", "ind")

def aggregate_areas_mc_all(
self,
query_file: str,
frequency: str,
mc_years: Optional[list[str]] = None,
areas_ids: Optional[list[str]] = None,
columns_names: Optional[list[str]] = None,
) -> pd.DataFrame:
"""
Creates a matrix of aggregated raw data for areas with mc-all
Args:
query_file: values from McAllAreas
frequency: values from Frequency
Returns: Pandas DataFrame corresponding to the aggregated raw data
"""
aggregation_entry = AggregationEntry(
query_file=MCAllAreas(query_file),
frequency=Frequency(frequency),
mc_years=mc_years,
type_ids=areas_ids,
columns_names=columns_names,
)

return self._output_service.aggregate_values(self.name, aggregation_entry, "areas", "all")

def aggregate_links_mc_all(
self,
query_file: str,
frequency: str,
mc_years: Optional[list[str]] = None,
areas_ids: Optional[list[str]] = None,
columns_names: Optional[list[str]] = None,
) -> pd.DataFrame:
"""
Creates a matrix of aggregated raw data for links with mc-all
Args:
query_file: values from McAllLinks
frequency: values from Frequency
Returns: Pandas DataFrame corresponding to the aggregated raw data
"""
aggregation_entry = AggregationEntry(
query_file=MCAllLinks(query_file),
frequency=Frequency(frequency),
mc_years=mc_years,
type_ids=areas_ids,
columns_names=columns_names,
)

return self._output_service.aggregate_values(self.name, aggregation_entry, "links", "all")
23 changes: 16 additions & 7 deletions src/antares/craft/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from pathlib import Path
from types import MappingProxyType
from typing import Dict, List, Optional, Union
from typing import List, Optional, Union

import pandas as pd

Expand All @@ -25,7 +25,11 @@
from antares.craft.config.local_configuration import LocalConfiguration
from antares.craft.exceptions.exceptions import APIError, LinkCreationError, StudyCreationError
from antares.craft.model.area import Area, AreaProperties, AreaUi
from antares.craft.model.binding_constraint import BindingConstraint, BindingConstraintProperties, ConstraintTerm
from antares.craft.model.binding_constraint import (
BindingConstraint,
BindingConstraintProperties,
ConstraintTerm,
)
from antares.craft.model.link import Link, LinkProperties, LinkUi
from antares.craft.model.output import Output
from antares.craft.model.settings.study_settings import DefaultStudySettings, StudySettings, StudySettingsLocal
Expand Down Expand Up @@ -186,6 +190,7 @@ def read_study_api(api_config: APIconf, study_id: str) -> "Study":

study.read_areas()
study.read_outputs()
study.read_binding_constraints()

return study

Expand Down Expand Up @@ -219,12 +224,11 @@ def __init__(
self._area_service = service_factory.create_area_service()
self._link_service = service_factory.create_link_service()
self._run_service = service_factory.create_run_service()
self._output_service = service_factory.create_output_service()
self._binding_constraints_service = service_factory.create_binding_constraints_service()
self._settings = DefaultStudySettings.model_validate(settings if settings is not None else StudySettings())
self._areas: Dict[str, Area] = dict()
self._links: Dict[str, Link] = dict()
self._binding_constraints: Dict[str, BindingConstraint] = dict()
self._areas: dict[str, Area] = dict()
self._links: dict[str, Link] = dict()
self._binding_constraints: dict[str, BindingConstraint] = dict()
self._outputs: dict[str, Output] = dict()

@property
Expand Down Expand Up @@ -329,6 +333,11 @@ def create_binding_constraint(
self._binding_constraints[binding_constraint.id] = binding_constraint
return binding_constraint

def read_binding_constraints(self) -> list[BindingConstraint]:
constraints = self._binding_constraints_service.read_binding_constraints()
self._binding_constraints = {constraint.id: constraint for constraint in constraints}
return constraints

def update_settings(self, settings: StudySettings) -> None:
new_settings = self._study_service.update_study_settings(settings)
if new_settings:
Expand Down Expand Up @@ -379,7 +388,7 @@ def read_outputs(self) -> list[Output]:
Returns: Output list
"""
outputs = self._output_service.read_outputs()
outputs = self._study_service.read_outputs()
self._outputs = {output.name: output for output in outputs}
return outputs

Expand Down
Loading

0 comments on commit 392c853

Please sign in to comment.