Skip to content

Commit 9af62fa

Browse files
authored
Version 0.2.0 (#4)
* Migrate candidate 0.2.0 version * delete version test
1 parent cbba2e4 commit 9af62fa

Some content is hidden

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

59 files changed

+1635
-951
lines changed

DEVELOPING.md

+6
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ docker run -e ORACLE_RANDOM_PASSWORD=true -e APP_USER=ixmp4_test -e APP_USER_PAS
253253
please note that you'll have to wait for a few seconds for the databases to be up and
254254
running.
255255

256+
The `tests/docker-compose.yml` file might help you accomplish all of this with a single command for your convenience.
257+
258+
```console
259+
docker-compose -f tests/docker-compose.yml up
260+
```
261+
256262
### Profiling
257263

258264
Some tests will output profiler information to the `.profiles/` directory (using the `profiled` fixture). You can analyze these using `snakeviz`. For example:

Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ ENV \
1818
PIP_DISABLE_PIP_VERSION_CHECK=on \
1919
PIP_DEFAULT_TIMEOUT=100
2020

21-
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python
21+
RUN pip install poetry
2222

2323
ENV PATH="$POETRY_HOME/bin:$PATH"
2424

2525
RUN poetry config virtualenvs.create false
2626

27-
ADD . /opt/ixmp4
27+
COPY . /opt/ixmp4
2828

2929
WORKDIR /opt/ixmp4
3030

doc/source/conf.py

+10
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,13 @@
6262
"python": ("https://docs.python.org/3", None),
6363
"pyam": ("https://pyam-iamc.readthedocs.io/en/stable/", None),
6464
}
65+
66+
# -- Prolog for all rst files ------------------------------------------------
67+
68+
rst_prolog = """
69+
70+
.. |br| raw:: html
71+
72+
<br>
73+
74+
"""

doc/source/data-model.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
The **ixmp4** Data Model
1+
The **ixmp4** data model
22
========================
33

44
The **ixmp4** package is a data warehouse for scenario data related to numerical modeling
55
of climate policy, energy systems transition and sustainable development.
66

77
This page is based on the section "The pyam data model" :cite:p:`Huppmann:2021:pyam-v1.0`.
88

9-
A Scenario Run
9+
A scenario run
1010
--------------
1111

1212
An ixmp4 database contains data for "scenario runs", which can be one of the following:
@@ -28,7 +28,7 @@ A scenario run is identified by the following *index* attributes:
2828

2929
A scenario run can have the data types shown below.
3030

31-
The IAMC Timeseries Data Format
31+
The IAMC timeseries data format
3232
-------------------------------
3333

3434
.. figure:: _static/iamc-logo.png
@@ -59,7 +59,7 @@ following the IAMC format from the Horizon 2020 `CD-LINKS`_ project.
5959

6060
.. _`CD-LINKS`: https://www.cd-links.org
6161

62-
Supported Time Domains
62+
Supported time domains
6363
^^^^^^^^^^^^^^^^^^^^^^
6464

6565
The implementation of the IAMC-format timeseries data format in **ixmp4** supports
@@ -80,7 +80,7 @@ A **run** can simultaneously have timeseries data for all temporal domains.
8080
of a :class:`pyam.IamDataFrame`.
8181
| Read the docs for the `pyam 'data' data model <https://pyam-iamc.readthedocs.io/en/stable/data.html#scenario-timeseries-data-data>`_.
8282
83-
Meta Indicators
83+
Meta indicators
8484
---------------
8585

8686
Meta indicators are intended for categorisation and quantitative indicators

doc/source/index.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Overview
2020
The **ixmp4** package is a data warehouse for high-powered scenario analysis
2121
in the domain of integrated assessment of climate change and energy systems modeling.
2222

23-
Table of Contents
23+
Table of contents
2424
-----------------
2525

2626
.. toctree::
@@ -51,11 +51,11 @@ Funding acknowledgement
5151

5252
.. figure:: _static/ariadne-bmbf-logo.png
5353
:align: left
54-
:height: 80px
54+
:height: 90px
5555

5656
The development of the **ixmp4** package was funded from the EU Horizon 2020 projects
5757
`openENTRANCE <https://openentrance.eu>`_ and `ECEMF <https://ecemf.eu>`_
58-
as well as the BMBF Kopernikus project `ARIADNE <https://ariadneprojekt.de>`_
58+
as well as the BMBF Kopernikus project `ARIADNE <https://ariadneprojekt.de>`_ |br|
5959
(FKZ 03SFK5A by the German Federal Ministry of Education and Research).
6060

6161
.. figure:: _static/EU-logo-300x201.jpg

docker/docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: "3"
33
services:
44
ixmp4_server:
55
restart: always
6-
image: ene-docker.iiasa.ac.at/ixmp4-server:latest
6+
image: ene-docker.iiasa.ac.at/ixmp4-server:dev
77
volumes:
88
- ./run:/opt/ixmp4/run
99
env_file:

docker/docker.env

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
IXMP4_MODE=development
22
IXMP4_DIR=/opt/ixmp4/run/ixmp
3+
IXMP4_SERVER_DIR=/opt/ixmp4/run
34
IXMP4_SECRET_HS256=secretkey
5+
IXMP4_MANAGER_URL=https://api.dev.manager.ece.iiasa.ac.at/v1/
6+
NUM_WORKERS=2
7+
NUM_WORKERS=2
8+

ixmp4/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# flake8: noqa
2+
import importlib.metadata
23

34
from ixmp4.core import (
45
Platform as Platform,
@@ -19,4 +20,4 @@
1920

2021
from ixmp4.data.abstract import DataPoint as DataPoint
2122

22-
__version__ = "0.1.0"
23+
__version__ = importlib.metadata.version("ixmp4")

ixmp4/cli/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ def login(
5858
)
5959

6060

61+
@app.command()
62+
def logout():
63+
if typer.confirm(
64+
"Are you sure you want to log out and delete locally saved credentials?"
65+
):
66+
settings.credentials.clear("default")
67+
68+
6169
try:
6270
import pytest
6371

ixmp4/cli/server.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from fastapi.openapi.utils import get_openapi
88
from ixmp4.server import v1
9+
from ixmp4.conf import settings
910

1011
from . import utils
1112

@@ -24,8 +25,14 @@ def start(
2425
),
2526
) -> None:
2627
"""Starts the ixmp4 web api."""
27-
28-
uvicorn.run("ixmp4.server:app", host=host, port=port, reload=True)
28+
reload = settings.mode != "production"
29+
uvicorn.run(
30+
"ixmp4.server:app",
31+
host=host,
32+
port=port,
33+
reload=reload,
34+
log_config="ixmp4/conf/logging/server.conf",
35+
)
2936

3037

3138
@app.command()

ixmp4/conf/__init__.py

+1-77
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,5 @@
11
from dotenv import load_dotenv
2-
from pathlib import Path
3-
4-
from pydantic import BaseSettings, Field, validator, HttpUrl, Extra
5-
6-
from ixmp4.core.exceptions import InvalidCredentials
7-
from .credentials import Credentials
8-
from .toml import TomlConfig
9-
from .manager import ManagerConfig
10-
from .auth import ManagerAuth
11-
from .user import local_user
12-
from .base import PlatformInfo as PlatformInfo
13-
14-
15-
class Settings(BaseSettings):
16-
mode: str = "production"
17-
storage_directory: Path = Field("~/.local/share/ixmp4/", env="ixmp4_dir")
18-
secret_hs256: str = "default_secret_hs256"
19-
migration_db_uri: str = "sqlite:///./run/db.sqlite"
20-
manager_url: HttpUrl = Field("https://api.manager.ece.iiasa.ac.at/v1")
21-
22-
class Config:
23-
env_prefix = "ixmp4_"
24-
extra = Extra.allow
25-
26-
def __init__(self, *args, **kwargs) -> None:
27-
super().__init__(*args, **kwargs)
28-
29-
self.storage_directory.mkdir(parents=True, exist_ok=True)
30-
31-
database_dir = self.storage_directory / "databases"
32-
database_dir.mkdir(exist_ok=True)
33-
self.load_credentials()
34-
self.load_manager_config()
35-
self.load_toml_config()
36-
37-
def load_credentials(self):
38-
credentials_config = self.storage_directory / "credentials.toml"
39-
credentials_config.touch()
40-
self.credentials = Credentials(credentials_config)
41-
42-
self.default_credentials = None
43-
self.default_auth = None
44-
try:
45-
self.default_credentials = self.credentials.get("default")
46-
except KeyError:
47-
# TODO: WARNING: No default credentials provided.
48-
pass
49-
50-
if self.default_credentials is not None:
51-
try:
52-
username, password = self.default_credentials
53-
self.default_auth = ManagerAuth(username, password, self.manager_url)
54-
except InvalidCredentials:
55-
# TODO: WARNING: Default credentials invalid.
56-
pass
57-
58-
def load_manager_config(self):
59-
self.manager = None
60-
if self.default_auth is not None:
61-
self.manager = ManagerConfig(
62-
self.manager_url, self.default_auth, remote=True
63-
)
64-
65-
def load_toml_config(self):
66-
if self.default_auth is not None:
67-
toml_user = self.default_auth.get_user()
68-
else:
69-
toml_user = local_user
70-
toml_config = self.storage_directory / "platforms.toml"
71-
toml_config.touch()
72-
self.toml = TomlConfig(toml_config, toml_user)
73-
74-
@validator("storage_directory")
75-
def expand_user(cls, v):
76-
# translate ~/asdf into /home/user/asdf
77-
return Path.expanduser(v)
78-
2+
from ixmp4.conf.settings import Settings
793

804
load_dotenv()
815
# strict typechecking fails due to a bug

ixmp4/conf/auth.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from uuid import uuid4
23
from datetime import datetime, timedelta
34

@@ -6,7 +7,9 @@
67

78
from ixmp4.core.exceptions import InvalidCredentials, IxmpError
89

9-
from .user import User
10+
from .user import User, anonymous_user
11+
12+
logger = logging.getLogger(__name__)
1013

1114

1215
class BaseAuth(object):
@@ -23,11 +26,11 @@ def get_user(self) -> User:
2326
class SelfSignedAuth(BaseAuth, httpx.Auth):
2427
"""Generates its own JWT with the supplied secret."""
2528

26-
def __init__(self, secret: str):
29+
def __init__(self, secret: str, username: str = "ixmp4"):
2730
self.secret = secret
2831
self.user = User(
2932
id=-1,
30-
username="ixmp4",
33+
username=username,
3134
3235
is_staff=True,
3336
is_superuser=True,
@@ -68,6 +71,18 @@ def get_user(self) -> User:
6871
return self.user
6972

7073

74+
class AnonymousAuth(BaseAuth, httpx.Auth):
75+
def __init__(self):
76+
self.user = anonymous_user
77+
logger.info("Connecting to service anonymously and without credentials.")
78+
79+
def __call__(self, r):
80+
return r
81+
82+
def get_user(self) -> User:
83+
return self.user
84+
85+
7186
class ManagerAuth(BaseAuth, httpx.Auth):
7287
"""Uses the SceSe AAC/Management Service to obtain and refresh a token."""
7388

@@ -104,7 +119,12 @@ def obtain_jwt(self):
104119
)
105120
if res.status_code >= 400:
106121
if res.status_code == 401:
107-
raise InvalidCredentials()
122+
raise InvalidCredentials(
123+
"Your credentials were rejected by the "
124+
"Scenario Services Management System. "
125+
"Check if they are correct and your account is active "
126+
"or log out with `ixmp4 logout` to use ixmp4 anonymously."
127+
)
108128
else:
109129
raise IxmpError("Unknown API error: " + res.text)
110130

ixmp4/conf/credentials.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from contextlib import suppress
12
from pathlib import Path
23
import rtoml as toml
34

@@ -26,3 +27,8 @@ def set(self, key: str, username: str, password: str):
2627
"password": password,
2728
}
2829
self.dump()
30+
31+
def clear(self, key: str):
32+
with suppress(KeyError):
33+
del self.credentials[key]
34+
self.dump()

ixmp4/conf/logging/debug.conf

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
[loggers]
3+
keys = root
4+
5+
[handlers]
6+
keys = console
7+
8+
[formatters]
9+
keys = generic
10+
11+
[logger_root]
12+
level = DEBUG
13+
handlers = console
14+
qualname =
15+
16+
[handler_console]
17+
class = StreamHandler
18+
args = (sys.stderr,)
19+
level = NOTSET
20+
formatter = generic
21+
22+
[formatter_generic]
23+
format = [%(levelname)s] %(asctime)s - %(name)s: %(message)s
24+
datefmt = %H:%M:%S

ixmp4/conf/logging/development.conf

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
[loggers]
3+
keys = root
4+
5+
[handlers]
6+
keys = console
7+
8+
[formatters]
9+
keys = generic
10+
11+
[logger_root]
12+
level = INFO
13+
handlers = console
14+
qualname =
15+
16+
[handler_console]
17+
class = StreamHandler
18+
args = (sys.stderr,)
19+
level = NOTSET
20+
formatter = generic
21+
22+
[formatter_generic]
23+
format = [%(levelname)s] %(asctime)s - %(name)s: %(message)s
24+
datefmt = %H:%M:%S

0 commit comments

Comments
 (0)