Skip to content

Commit

Permalink
Merge pull request #1 from ait-energy/dev
Browse files Browse the repository at this point in the history
Set up basic CI
  • Loading branch information
sstroemer authored Jun 11, 2024
2 parents 2b2c3ff + 1fe7a22 commit fdcdf28
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 24 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Run Python tests

on: [push, pull_request]

jobs:
run_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
run: poetry install --with test
- name: Run the automated tests
run: poetry run pytest
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: ait-energy/iesopt
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import sys
from pathlib import Path

from sphinxawesome_theme.postprocess import Icons


# -- Setup -------------------------------------------------------------------

Expand Down Expand Up @@ -37,8 +39,6 @@
html_theme = "sphinxawesome_theme"
html_static_path = ["_static"]

from sphinxawesome_theme.postprocess import Icons

html_permalinks_icon = Icons.permalinks_icon

pygments_style = "default"
Expand Down
35 changes: 35 additions & 0 deletions docs/pages/dev/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Tests & Code Quality

## Format and lint

Use

```bash
black .
ruff .
```

Commit pure formatting changes using `chore: formatting`.

## Running tests

```bash
pytest
```

This will print a coverage report, which looks like

```text
Name Stmts Miss Cover
--------------------------------------------------
src/iesopt/__init__.py 33 4 88%
src/iesopt/general.py 0 0 100%
...
src/iesopt/util/logging.py 7 0 100%
src/iesopt/util/util.py 5 0 100%
--------------------------------------------------
TOTAL 417 172 59%
```

and tells you roughly how good each file is covered by automated tests. Further it creates a `coverage.xml` file in the project root folder, that you can use. In `VSCode`, the extension [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) allows inspecting test coverage directly in your code editor. If you have it installed and loaded, simply hit `Ctrl + Shift + 8` (if you are using default keybinds) to enable watching the coverage.

10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pathlib = "^1.0.0"
juliacall = "^0.9.20"
pandas = "^2.0.0"
juliapkg = "^0.1.13"
pytest-ruff = "^0.3.2"

[tool.poetry.group.dev.dependencies]
ruff = "^0.4.7"
Expand All @@ -63,4 +64,11 @@ extend-exclude = ["*.jl"]
target-version = "py311"

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
addopts = [
"--import-mode=importlib",
"--ruff",
"--ruff-format",
"--cov-report=term",
"--cov-report=xml:coverage.xml",
"--cov=iesopt",
]
12 changes: 6 additions & 6 deletions src/iesopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ def jump_shadow_price(item):
# =======================================================================

# Setup Julia.
from .julia import initialize as _initialize_everything
from .util import get_iesopt_module_attr as _get_iesopt_module_attr
from .julia import initialize as _initialize_everything # noqa: E402
from .util import get_iesopt_module_attr as _get_iesopt_module_attr # noqa: E402

julia = _initialize_everything()

# Export everything.
from iesopt.model import Model
from iesopt.results import Results
from iesopt.iesopt import run, examples, make_example
from iesopt.model import Model as Model # noqa: E402
from iesopt.results import Results as Results # noqa: E402
from iesopt.iesopt import run as run, examples as examples, make_example as make_example # noqa: E402

from .julia.util import jl_symbol
from .julia.util import jl_symbol # noqa: E402

Symbol = jl_symbol

Expand Down
5 changes: 2 additions & 3 deletions src/iesopt/julia/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from .general import initialize

from .util import jl_safe_seval, jl_import
from .general import initialize as initialize
from .util import jl_import as jl_import
2 changes: 1 addition & 1 deletion src/iesopt/julia/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def setup_julia():
# Setup Julia (checking if it "looks" valid).
import juliapkg

if juliapkg.resolve() != True:
if not juliapkg.resolve():
raise Exception("Julia setup is not valid")

import juliacall
Expand Down
16 changes: 8 additions & 8 deletions src/iesopt/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def generate(self) -> None:
logger.error(f"Exception during `generate`: {e}")
try:
logger.error(f"Current debugging info: {self.data.debug}")
except:
except Exception as e:
logger.error("Failed to extract debugging info")

def optimize(self) -> None:
Expand Down Expand Up @@ -122,7 +122,7 @@ def optimize(self) -> None:
logger.error(f"Exception during `optimize`: {e}")
try:
logger.error(f"Current debugging info: {self.data.debug}")
except:
except Exception as e:
logger.error("Failed to extract debugging info")

@property
Expand All @@ -136,16 +136,16 @@ def extract_result(self, component: str, field: str, mode: str = "value"):
"""Manually extract a specific result from the model."""
try:
c = self._IESopt.component(self.core, "node2")
except:
except Exception:
raise Exception(f"Exception during `extract_result({component}, {field}, mode={mode})`")

f = None
for fieldtype in ["var", "exp", "con", "obj"]:
try:
t = getattr(component, fieldtype)
t = getattr(c, fieldtype)
f = getattr(t, field)
break
except:
except Exception:
pass

if f is None:
Expand All @@ -158,14 +158,14 @@ def extract_result(self, component: str, field: str, mode: str = "value"):
return self._jump_duals(f)
else:
raise Exception(f"Mode `{mode}` not supported, use `value` or `dual`")
except:
except Exception:
raise Exception(f"Error during extraction of result `{field}` from component `{component}`")

def get_component(self, component: str):
"""Get a core component based on its full name."""
try:
return self._IESopt.component(self.core, component)
except:
except Exception:
raise Exception(f"Error while retrieving component `{component}` from model")

def get_variable(self, component: str, variable: str):
Expand All @@ -192,7 +192,7 @@ def nvar(self, var: str):
"""
try:
return self.core[jl_symbol(var)]
except:
except Exception:
raise Exception(f"Error while retrieving variable `{var}` from model")

@staticmethod
Expand Down
7 changes: 5 additions & 2 deletions src/iesopt/util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from .logging import logger
from .util import set_iesopt_module_attr, get_iesopt_module_attr
from .logging import logger as logger
from .util import (
set_iesopt_module_attr as set_iesopt_module_attr,
get_iesopt_module_attr as get_iesopt_module_attr,
)
2 changes: 1 addition & 1 deletion tests/test_iesoptlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ def test_make_example(self, tmp_path):
def test_run_example(self, tmp_path):
config_file = iesopt.make_example("01_basic_single_node", dst_dir=tmp_path, dst_name="config")
model = iesopt.run(config_file, verbosity=False)
# assert model is not None
assert model is not None
# assert model.status == "Optimal"
# assert model.objective_value is not None

0 comments on commit fdcdf28

Please sign in to comment.