Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: save/fetch Reporter as DataSource #751

Merged
merged 90 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
6684258
feat: revision to add `attributes` column to the `data_source` table
victorgarcia98 Jun 6, 2023
abfe310
feat: add `attributes` column to the DataSource model
victorgarcia98 Jun 6, 2023
c5fddad
feat: add sensors relationship in DataSource
victorgarcia98 Jun 6, 2023
c2de8fc
fix: make sensors relationship viewonly
victorgarcia98 Jun 20, 2023
a26ed8e
feat: add report_config to Reporter class
victorgarcia98 Jun 6, 2023
d9cef3d
feat: add PandasReporter report and reporter schemas
victorgarcia98 Jun 6, 2023
d2ef590
fix: update fixture by removing beliefs_search_configs and adding inp…
victorgarcia98 Jun 6, 2023
54b5dda
feat: add report config to PandasReporter
victorgarcia98 Jun 6, 2023
8028431
feat: add helper methods to DataSource
victorgarcia98 Jun 23, 2023
364c8ec
Merge branch 'feature/add-attributes-column-data-source' into feature…
victorgarcia98 Jun 23, 2023
fd175d4
fix: modernize AggregatorReporter
victorgarcia98 Jun 23, 2023
4567c23
feat: add attributes hash
victorgarcia98 Jun 26, 2023
773dc9d
feat: add attributes to the function get_or_create_source
victorgarcia98 Jun 26, 2023
4ec5d3e
Merge branch 'feature/reporting/add-attributes-column-data-source' in…
victorgarcia98 Jun 26, 2023
6d632b8
feat: add attribute hash to get_or_create_source
victorgarcia98 Jun 26, 2023
696d363
Merge branch 'feature/reporting/add-attributes-column-data-source' in…
victorgarcia98 Jun 26, 2023
68eae91
feat: save/fetch data generator to/from data source
victorgarcia98 Jun 26, 2023
3049416
refactor: adapt reporters to use new DataGenerator class
victorgarcia98 Jun 26, 2023
76cf197
fix: use default method on load
victorgarcia98 Jun 28, 2023
39f4023
fix: adapt tests of the schemas
victorgarcia98 Jun 27, 2023
d19c2fa
fix: use a DataGenerator with a schema defined
victorgarcia98 Jun 28, 2023
8670d6c
Merge branch 'main' into feature/reporting/add-attributes-column-data…
victorgarcia98 Jun 28, 2023
793ac22
Merge branch 'feature/reporting/add-attributes-column-data-source' in…
victorgarcia98 Jun 28, 2023
c644875
Merge branch 'main' into feature/reporting/add-attributes-column-data…
victorgarcia98 Jun 29, 2023
ef63067
changing backref from "dynamic" to "select"
victorgarcia98 Jun 29, 2023
8ea2702
feat: add hash_attributes static method
victorgarcia98 Jun 29, 2023
e717a4e
fix: use hash_attributes static method
victorgarcia98 Jun 29, 2023
ec3b370
feat: adding attributes_hash to the DataSource unique constraint list
victorgarcia98 Jun 29, 2023
ab4b0ae
fix: add constraint to migration and downgrade
victorgarcia98 Jun 29, 2023
6b3a585
fix: only returning keys from the attributes field
victorgarcia98 Jun 29, 2023
3d373df
Merge remote-tracking branch 'origin/feature/reporting/add-attributes…
victorgarcia98 Jun 29, 2023
8748a3b
Merge branch 'feature/reporting/add-attributes-column-data-source' in…
victorgarcia98 Jun 29, 2023
3a88e00
refactor: rename _inputs_schema to _input_schema
victorgarcia98 Jun 29, 2023
ad9d24b
fix: typing
victorgarcia98 Jun 29, 2023
33d4522
fix: avoid future data leakage
victorgarcia98 Jun 29, 2023
1119c1f
refactor: rename PandasReporterInputConfigSchema to PandasReporterInp…
victorgarcia98 Jun 29, 2023
dc800a4
docs: clarify description of the fake_data mock variable
victorgarcia98 Jun 29, 2023
54b67f7
docs: fix docstring
victorgarcia98 Jul 2, 2023
1c75541
fix: use default value
victorgarcia98 Jul 2, 2023
9211978
fix: allow creating new attributes with the method `set_attributes`
victorgarcia98 Jul 2, 2023
dbf01b6
docs: add changelog entry
victorgarcia98 Jul 2, 2023
95518e0
docs: fix docstring
victorgarcia98 Jul 2, 2023
3cdf91b
fix: use default value
victorgarcia98 Jul 2, 2023
b347cb2
fix: allow creating new attributes with the method `set_attributes`
victorgarcia98 Jul 2, 2023
6c55893
docs: add changelog entry
victorgarcia98 Jul 2, 2023
ba6d14c
Merge branch 'feature/reporting/add-attributes-column-data-source' in…
victorgarcia98 Jul 2, 2023
e565198
fix: add reporters and schedulers into the data_generators attribute …
victorgarcia98 Jul 3, 2023
1b648ad
fix: raise Exceptions instead of returning None
victorgarcia98 Jul 3, 2023
b9c0b3f
fix: move sensor attribute from config to inputs
victorgarcia98 Jul 3, 2023
f239be4
fix: use same structure for data generators and add test
victorgarcia98 Jul 3, 2023
eac2d7c
refactor: use input_resolution instead of resolution
victorgarcia98 Jul 3, 2023
d303801
doc: update schema docstring
victorgarcia98 Jul 3, 2023
2abbeb6
refactor: rename input_resolution to resolution
victorgarcia98 Jul 3, 2023
9f8bd11
fix: remove sensor from config
victorgarcia98 Jul 3, 2023
006661a
docs: add comment
victorgarcia98 Jul 3, 2023
076a653
fix: remove df_output
victorgarcia98 Jul 3, 2023
d493dff
Merge remote-tracking branch 'origin/feature/reporting/save-reporters…
victorgarcia98 Jul 3, 2023
36d3856
fix:. for data in data["data"] haha
victorgarcia98 Jul 3, 2023
6169cd3
doc: add docstring to compute and __init__ in DataGenerator
victorgarcia98 Jul 3, 2023
a695d8b
refactor: rename inputs to input
victorgarcia98 Jul 3, 2023
091283b
fix: removing constructor
victorgarcia98 Jul 3, 2023
52289f6
docs: improve docstring
victorgarcia98 Jul 3, 2023
139b842
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Jul 6, 2023
5520c73
test: add data to confest
victorgarcia98 Jul 7, 2023
3bb13e2
test: add test_dst_transition
victorgarcia98 Jul 7, 2023
d51fc39
fix: never returning None
victorgarcia98 Jul 7, 2023
e9564d9
test: add test to check timely-beliefs resampling and calling an aggr…
victorgarcia98 Jul 9, 2023
81b6269
Merge remote-tracking branch 'origin/feature/reporting/save-reporters…
victorgarcia98 Jul 9, 2023
132cf46
test: change output sensor id
victorgarcia98 Jul 10, 2023
ff2f478
docs: add docstring to the data_source propert of the class DataGener…
victorgarcia98 Jul 10, 2023
0fc8de2
docs: edit data_source docstring
victorgarcia98 Jul 10, 2023
2c4f8bc
refactor: ranming input_sensors to input_variables
victorgarcia98 Jul 10, 2023
9daa2bb
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Jul 10, 2023
d6daace
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Jul 20, 2023
50d0406
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Aug 2, 2023
4673492
rename input to parameters
victorgarcia98 Aug 2, 2023
0a4e302
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Aug 3, 2023
c2e37a9
remove unnecessary import
victorgarcia98 Aug 3, 2023
cfc554e
add save_config attribute
victorgarcia98 Aug 3, 2023
18f3b02
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Aug 3, 2023
b389951
remove leftover comment
victorgarcia98 Aug 3, 2023
59de8ea
add inline comments
victorgarcia98 Aug 3, 2023
abd24d7
Merge remote-tracking branch 'origin/main' into feature/reporting/sav…
victorgarcia98 Aug 3, 2023
c035f40
Merge branch 'main' into feature/reporting/save-reporters-data-source
victorgarcia98 Aug 3, 2023
8470fed
deprecation message for app.reoprters and app.schedulers
victorgarcia98 Aug 3, 2023
a55e661
fix typo
victorgarcia98 Aug 3, 2023
1ed7e5e
use data source created by the data generator
victorgarcia98 Aug 3, 2023
4b676b0
Merge remote-tracking branch 'origin/feature/reporting/save-reporters…
victorgarcia98 Aug 3, 2023
1e8704e
feat: support YAML in `flexmeasures add report` command (#752)
victorgarcia98 Aug 3, 2023
0a38d76
Polish `PandasReporter` schemas (#788)
victorgarcia98 Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"workbench.editor.wrapTabs": true,
"python.formatting.provider": "black"
"python.formatting.provider": "black",
"python.testing.pytestArgs": [
"flexmeasures"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
11 changes: 9 additions & 2 deletions flexmeasures/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

import time
from copy import copy
import os
from pathlib import Path
from datetime import date
Expand Down Expand Up @@ -121,8 +122,14 @@ def create( # noqa C901
from flexmeasures.utils.coding_utils import get_classes_module
from flexmeasures.data.models import reporting, planning

app.reporters = get_classes_module("flexmeasures.data.models", reporting.Reporter)
app.schedulers = get_classes_module("flexmeasures.data.models", planning.Scheduler)
reporters = get_classes_module("flexmeasures.data.models", reporting.Reporter)
schedulers = get_classes_module("flexmeasures.data.models", planning.Scheduler)

app.reporters = reporters
app.schedulers = schedulers

app.data_generators = copy(reporters) # use copy to avoid mutating app.reporters
app.data_generators.update(schedulers)

# add auth policy

Expand Down
8 changes: 4 additions & 4 deletions flexmeasures/cli/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def setup_dummy_data(db, app):

@pytest.fixture(scope="module")
@pytest.mark.skip_github
def reporter_config_raw(app, db, setup_dummy_data):
def reporter_config(app, db, setup_dummy_data):
"""
This reporter_config defines the operations to add up the
values of the sensors 1 and 2 and resamples the result to a
Expand All @@ -81,8 +81,8 @@ def reporter_config_raw(app, db, setup_dummy_data):

sensor1, sensor2, report_sensor = setup_dummy_data

reporter_config_raw = dict(
beliefs_search_configs=[dict(sensor=sensor1.id), dict(sensor=sensor2.id)],
reporter_config = dict(
input_variables=["sensor_1", "sensor_2"],
transformations=[
dict(
df_input="sensor_1",
Expand All @@ -95,4 +95,4 @@ def reporter_config_raw(app, db, setup_dummy_data):
final_df_output="df_agg",
)

return reporter_config_raw
return reporter_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""add attribute column to data source

Revision ID: 2ac7fb39ce0c
Revises: d814c0688ae0
Create Date: 2023-06-05 23:41:31.788961

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "2ac7fb39ce0c"
down_revision = "d814c0688ae0"
branch_labels = None
depends_on = None


def upgrade():
# add the column `attributes` to the table `data_source`
op.add_column(
"data_source",
sa.Column("attributes", sa.JSON(), nullable=True, default={}),
)

# add the column `attributes_hash` to the table `data_source`
op.add_column(
"data_source",
sa.Column("attributes_hash", sa.LargeBinary(length=256), nullable=True),
)


def downgrade():
pass
140 changes: 129 additions & 11 deletions flexmeasures/data/models/data_sources.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
from __future__ import annotations

from typing import TYPE_CHECKING
import json
from typing import TYPE_CHECKING, Any, Type
from sqlalchemy.ext.mutable import MutableDict

import timely_beliefs as tb

from flexmeasures.data import db
from flask import current_app
import hashlib

from marshmallow import Schema


if TYPE_CHECKING:
from flexmeasures.data.models.user import User


class DataGeneratorMixin:
class DataGenerator:
_data_source: DataSource | None = None

_config: dict = None

_inputs_schema: Type[Schema] | None = None
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
_config_schema: Type[Schema] | None = None
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, config: dict | None = None, **kwargs) -> None:
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
if config is None:
_config = kwargs
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
else:
if self._config_schema:
_config = self._config_schema.load(config)
else:
_config = config
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved

self._config = _config

def _compute(self, **kwargs):
raise NotImplementedError()

def compute(self, inputs: dict = None, **kwargs):
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
if inputs is None:
_inputs = kwargs
self.validate_deserialized_inputs(_inputs)
else:
if self._inputs_schema:
_inputs = self._inputs_schema.load(inputs)

else: # skip validation
_inputs = inputs
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved

return self._compute(**_inputs)

def validate_deserialized_inputs(self, inputs: dict):
self._inputs_schema.load(self._inputs_schema.dump(inputs))

@classmethod
def get_data_source_info(cls: type) -> dict:
"""
Expand All @@ -23,33 +63,35 @@ def get_data_source_info(cls: type) -> dict:
See for instance get_data_source_for_job().
"""
source_info = dict(
name=current_app.config.get("FLEXMEASURES_DEFAULT_DATASOURCE")
source=current_app.config.get("FLEXMEASURES_DEFAULT_DATASOURCE")
) # default

from flexmeasures.data.models.planning import Scheduler
from flexmeasures.data.models.reporting import Reporter

if issubclass(cls, Reporter):
source_info["type"] = "reporter"
source_info["source_type"] = "reporter"
elif issubclass(cls, Scheduler):
source_info["type"] = "scheduler"
source_info["source_type"] = "scheduler"
else:
source_info["type"] = "undefined"
source_info["source_type"] = "undefined"

source_info["model"] = cls.__name__

return source_info

@property
def data_source(self):
def data_source(self) -> "DataSource | None":
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
from flexmeasures.data.services.data_sources import get_or_create_source

if self._data_source is None:
data_source_info = self.get_data_source_info()

self._data_source = get_or_create_source(
source=data_source_info.get("name"),
source_type=data_source_info.get("type"),
data_source_info["attributes"] = dict(
config=self._config_schema.dump(self._config)
)

self._data_source = get_or_create_source(**data_source_info)

return self._data_source


Expand All @@ -68,18 +110,32 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin):
)
user = db.relationship("User", backref=db.backref("data_source", lazy=True))

attributes = db.Column(MutableDict.as_mutable(db.JSON), nullable=False, default={})

attributes_hash = db.Column(db.LargeBinary(length=256))

# The model and version of a script source
model = db.Column(db.String(80), nullable=True)
version = db.Column(
db.String(17), # length supports up to version 999.999.999dev999
nullable=True,
)

sensors = db.relationship(
"Sensor",
secondary="timed_belief",
backref=db.backref("data_sources", lazy="dynamic"),
viewonly=True,
)

_data_generator: DataGenerator | None = None

def __init__(
self,
name: str | None = None,
type: str | None = None,
user: User | None = None,
attributes: dict | None = None,
**kwargs,
):
if user is not None:
Expand All @@ -89,9 +145,53 @@ def __init__(
elif user is None and type == "user":
raise TypeError("A data source cannot have type 'user' but no user set.")
self.type = type

if attributes is not None:
self.attributes = attributes
self.attributes_hash = hashlib.sha256(
json.dumps(attributes).encode("utf-8")
).digest()

tb.BeliefSourceDBMixin.__init__(self, name=name)
db.Model.__init__(self, **kwargs)

@property
def data_generator(self):
if self._data_generator:
return self._data_generator

data_generator = None

if self.type not in ["scheduler", "forecaster", "reporter"]:
current_app.logger.warning(
"Only the classes Scheduler, Forecaster and Reporters are DataGenerator's."
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
)
return None

if not self.model:
current_app.logger.warning(
"There's no DataGenerator class defined in this DataSource."
)
return None

if self.model not in current_app.data_generators:
current_app.logger.warning(
"DataGenerator `{self.model}` not registered in this FlexMeasures instance."
)
return None

# fetch DataGenerator details
data_generator_details = self.attributes.get("data_generator", {})
config = data_generator_details.get("config", {})
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved

# create DataGenerator class and assign the current DataSource (self) as its source
data_generator = current_app.data_generators[self.model](config=config)
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
data_generator._data_source = self

self._data_generator = data_generator

return self._data_generator

@property
def label(self):
"""Human-readable label (preferably not starting with a capital letter, so it can be used in a sentence)."""
Expand Down Expand Up @@ -144,3 +244,21 @@ def to_dict(self) -> dict:
type=self.type if self.type in ("forecaster", "scheduler") else "other",
description=self.description,
)

def get_attribute(self, attribute: str, default: Any = None) -> Any:
"""Looks for the attribute on the DataSource.
If not found, returns the default.
"""
if hasattr(self, attribute):
return getattr(self, attribute)
if attribute in self.attributes:
return self.attributes[attribute]

return default

def has_attribute(self, attribute: str) -> bool:
return attribute in self.attributes

def set_attribute(self, attribute: str, value):
if self.has_attribute(attribute):
self.attributes[attribute] = value