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

Issue 247 generic asset crud #290

Merged
merged 35 commits into from Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a36eeaa
add a new method to specify an auth context (by callable) and discuss…
nhoening Dec 29, 2021
00d5651
add ACL for GenericAsset
nhoening Dec 29, 2021
63ee1fd
added /dev/generic_assets API, CRUD uses it (passing manual testing)
nhoening Dec 29, 2021
02edb47
fix asset icon in asset view
nhoening Dec 29, 2021
c6a0b50
remove verbs from /api/dev/generic_assets API routes (HTTP method alr…
nhoening Dec 30, 2021
6018004
make CRUD UI tests (mocking the asset API) work
nhoening Dec 30, 2021
e922e39
auth policy: change 'create' to 'create-children' permission (removin…
nhoening Dec 30, 2021
4c6e213
separate fresh tests from others; only generate copies of generic ass…
nhoening Jan 1, 2022
f304a98
add missing mock in another ui test
nhoening Jan 1, 2022
b0fbff6
add unique contraints: on GenericAssetType.name & on GenericAssets fo…
nhoening Jan 1, 2022
3982e0f
improve explanation of our authorization policy implementation
nhoening Jan 1, 2022
2ae8aad
update changelog
nhoening Jan 1, 2022
fdfca9c
improve changelog, also mention temporary API location
nhoening Jan 3, 2022
4c95ff9
comments next to test cases
nhoening Jan 3, 2022
5518c17
create_user function hashes the password
nhoening Jan 3, 2022
0c492a0
more informative validation errror
nhoening Jan 3, 2022
2ab9f5b
allow creation of public Assets in UI
nhoening Jan 3, 2022
f75982c
Fix display of owning account
nhoening Jan 3, 2022
912b81e
better changelog docs
nhoening Jan 3, 2022
7d7e5a1
list sensors on asset page
nhoening Jan 4, 2022
b2479db
do not fail getting latest state if capacity_in_mw is not in attributes
nhoening Jan 4, 2022
850b92d
better default for capacity_in_mw
nhoening Jan 4, 2022
c5ce5a9
Merge branch 'project-9' into issue-247-GenericAsset-CRUD
nhoening Jan 4, 2022
2d463bb
use id in the dict, for sensors lookup
nhoening Jan 4, 2022
a7f1d7f
Suggested fixes to pr 290 (#299)
Flix6x Jan 4, 2022
50248bb
small improvement on locating ea addresses
nhoening Jan 4, 2022
8359502
Describe current state of transition
nhoening Jan 4, 2022
d50c4f4
deprecation notice in old-style asset API documentation
nhoening Jan 4, 2022
133e04b
Fix type annotation
Flix6x Jan 4, 2022
93c9a69
Update documentation
Flix6x Jan 4, 2022
c4262ce
Update v2.0 connection entity addresses in documentation
Flix6x Jan 4, 2022
f9633e5
Correct quickrefs in v2.0 documentation
Flix6x Jan 4, 2022
81d6f6a
Update other v2.0 sensor entity addresses in documentation
Flix6x Jan 5, 2022
70f180c
Fix entity address dates corresponding to older previous domain name
Flix6x Jan 5, 2022
9165e79
Adapt more occurrences of older dates in entity addresses, and switch…
Flix6x Jan 5, 2022
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
8 changes: 7 additions & 1 deletion documentation/api/introduction.rst
Expand Up @@ -204,12 +204,18 @@ Here is a full example for a FlexMeasures connection address:

where FlexMeasures runs at `company.flexmeasures.io` (which the current domain owner started using in February 2021), and the locally unique string is of scheme `fm0` (see below) and the asset ID is 73. The asset's owner ID is 30, but this part is optional.

Both the owner ID and the asset ID, as well as the full entity address can be obtained on the asset's listing:
Assets are listed at:

.. code-block:: html

https://company.flexmeasures.io/assets

Both the owner ID and the full entity address can be obtained on the asset's page, e.g.:

.. code-block:: html

https://company.flexmeasures.io/assets/73


Entity address structure
""""""""""""""""""""""""""
Expand Down
3 changes: 2 additions & 1 deletion documentation/changelog.rst
Expand Up @@ -6,7 +6,7 @@ v0.8.0 | November XX, 2021
===========================

.. warning:: Upgrading to this version requires running ``flexmeasures db upgrade`` (you can create a backup first with ``flexmeasures db-ops dump``).
.. warning:: Changes to asset attributes made via the UI or API are not reflected in the new data model until `issue #247 <https://github.com/FlexMeasures/flexmeasures/issues/247>`_ is resolved.
.. note:: v0.8.0 is doing much of the work we need to do to move to the new data model (see :ref:`note_on_datamodel_transition`). We hope to keep the migration steps for users very limited. One thing you'll notice is that we are copying over existing data to the new model (which will be kept in sync) with the `db upgrade` command (see warning above), which can take a few minutes.

New features
-----------
Expand All @@ -25,6 +25,7 @@ Infrastructure / Support
* Add sensor method to obtain just its latest state (excl. forecasts) [see `PR #235 <http://www.github.com/FlexMeasures/flexmeasures/pull/235>`_]
* Migrate attributes of assets, markets and weather sensors to our new sensor model [see `PR #254 <http://www.github.com/FlexMeasures/flexmeasures/pull/254>`_ and `project 9 <http://www.github.com/FlexMeasures/flexmeasures/projects/9>`_]
* Migrate all time series data to our new sensor data model based on the `timely beliefs <https://github.com/SeitaBV/timely-beliefs>`_ lib [see `PR #286 <http://www.github.com/FlexMeasures/flexmeasures/pull/286>`_ and `project 9 <http://www.github.com/FlexMeasures/flexmeasures/projects/9>`_]
* Support the new asset model (which describes the organisational structure, rather than sensors and data) in UI and API. Until the transition to our new data model is completed, the new API for assets is at `/api/dev/generic_assets`. [see `PR #251 <http://www.github.com/FlexMeasures/flexmeasures/pull/251>`_ and `PR #290 <http://www.github.com/FlexMeasures/flexmeasures/pulls/290>`_]


v0.7.1 | November 08, 2021
Expand Down
10 changes: 10 additions & 0 deletions documentation/dev/note-on-datamodel-transition.rst
Expand Up @@ -58,3 +58,13 @@ Here is a brief list:
- `Sensor relations and GeneralizedAssets with metadata <https://github.com/FlexMeasures/flexmeasures/projects/9>`_: We are generalizing our database structure for organising energy data, to support all sorts of sensors and relationships between them. We do this so we can better support the diverse set of use cases for energy flexibility.
- `UI views for GeneralizedAssets <https://github.com/FlexMeasures/flexmeasures/projects/10>`_: We are updating our UI views (dashboard maps and analytics charts) according to our new database structure for organising energy data. We do this so users can customize what they want to see.
- `Deprecate old database models <https://github.com/FlexMeasures/flexmeasures/projects/11>`_: We are deprecating the Power, Price and Weather tables in favour of the TimedBelief table, and deprecating the Asset, Market and WeatherSensor tables in favour of the Sensor and GeneralizedAsset tables. We are doing this so users can move their data to the new database model.


The state of the transition (January 2022, v0.8.0)
---------------------------------------------------

Project 9 was implemented, which moved a lot of structure over, as well as actual data and some UI (dashboard, assets). We believe that was the hardest part.

We are now close to being able to deprecate the old database models and route the API to the new model (see project 11). The API for assets is still in place, but the new one is already working (at /api/dev/generic_assets) and is powering what is shown in the UI.

We take care to support people on the old data mode so the transition will be as smooth as possible, as we said above. One part of this is that the ``flexmeasures db upgrade`` command copies your data to the new model. Also, creating new data (e.g. old-style assets) creates new-style data (e.g. assets/sensors) automatically. However, some edge cases are not supported in this way. For instance, edited asset meta data might have to be re-entered later. Feel free to contact us to discuss the transition if needed.
nhoening marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 10 additions & 8 deletions flexmeasures/api/common/schemas/users.py
Expand Up @@ -10,21 +10,23 @@ class AccountIdField(fields.Integer):
Field that represents an account ID. It de-serializes from the account id to an account instance.
"""

def __init__(self, *args, **kwargs):
kwargs["load_default"] = (
lambda: current_user.account if not current_user.is_anonymous else None
)
super().__init__(*args, **kwargs)

def _deserialize(self, account_id: int, attr, obj, **kwargs) -> Account:
def _deserialize(self, account_id: str, attr, obj, **kwargs) -> Account:
account: Account = Account.query.filter_by(id=int(account_id)).one_or_none()
if account is None:
raise abort(404, f"Account {id} not found")
raise abort(404, f"Account {account_id} not found")
return account

def _serialize(self, account: Account, attr, data, **kwargs) -> int:
return account.id

@classmethod
def load_current(cls):
"""
Use this with the load_default arg to __init__ if you want the current user's account
by default.
"""
return current_user.account if not current_user.is_anonymous else None


class UserIdField(fields.Integer):
"""
Expand Down
6 changes: 5 additions & 1 deletion flexmeasures/api/dev/__init__.py
Expand Up @@ -8,9 +8,13 @@ def register_at(app: Flask):
"""This can be used to register FlaskViews."""

from flexmeasures.api.dev.sensors import SensorAPI
from flexmeasures.api.dev.assets import AssetAPI
from flexmeasures.api.dev.sensor_data import post_data as post_sensor_data_impl

SensorAPI.register(app, route_prefix="/api/dev")
dev_api_prefix = "/api/dev"

SensorAPI.register(app, route_prefix=dev_api_prefix)
AssetAPI.register(app, route_prefix=dev_api_prefix)

@app.route("/sensorData", methods=["POST"])
@auth_token_required
Expand Down
86 changes: 86 additions & 0 deletions flexmeasures/api/dev/assets.py
@@ -0,0 +1,86 @@
from flask import current_app
from flask_classful import FlaskView, route
from flask_json import as_json
from webargs.flaskparser import use_kwargs, use_args

from flexmeasures.auth.decorators import permission_required_for_context
from flexmeasures.data.models.user import Account
from flexmeasures.data.models.generic_assets import GenericAsset as AssetModel
from flexmeasures.data.schemas.generic_assets import GenericAssetSchema as AssetSchema
from flexmeasures.api.common.schemas.generic_assets import AssetIdField
from flexmeasures.api.common.schemas.users import AccountIdField
from flexmeasures.data.config import db


asset_schema = AssetSchema()
assets_schema = AssetSchema(many=True)


class AssetAPI(FlaskView):
"""
This API view exposes generic assets.
Under development until it replaces the original Asset API.
"""

route_base = "/generic_assets"

@route("/", methods=["GET"])
@use_kwargs(
{
"account": AccountIdField(
data_key="account_id", load_default=AccountIdField.load_current
),
},
location="query",
)
@permission_required_for_context("read", arg_name="account")
@as_json
def index(self, account: Account):
"""List all assets owned by a certain account."""
return assets_schema.dump(account.generic_assets), 200

@route("/", methods=["POST"])
@permission_required_for_context(
"create-children", arg_loader=AccountIdField.load_current
)
@use_args(AssetSchema())
def post(self, asset_data):
"""Create new asset"""
asset = AssetModel(**asset_data)
db.session.add(asset)
db.session.commit()
return asset_schema.dump(asset), 201

@route("/<id>", methods=["GET"])
@use_kwargs({"asset": AssetIdField(data_key="id")}, location="path")
@permission_required_for_context("read", arg_name="asset")
@as_json
def fetch_one(self, id, asset):
"""Fetch a given asset"""
return asset_schema.dump(asset), 200

@route("/<id>", methods=["PATCH"])
@use_args(AssetSchema(partial=True))
@use_kwargs({"db_asset": AssetIdField(data_key="id")}, location="path")
@permission_required_for_context("update", arg_name="db_asset")
@as_json
def patch(self, asset_data, id, db_asset):
"""Update an asset given its identifier"""
ignored_fields = ["id", "account_id"]
for k, v in [(k, v) for k, v in asset_data.items() if k not in ignored_fields]:
setattr(db_asset, k, v)
db.session.add(db_asset)
db.session.commit()
return asset_schema.dump(db_asset), 200

@route("/<id>", methods=["DELETE"])
@use_kwargs({"asset": AssetIdField(data_key="id")}, location="path")
@permission_required_for_context("delete", arg_name="asset")
@as_json
def delete(self, id, asset):
"""Delete an asset given its identifier"""
asset_name = asset.name
db.session.delete(asset)
db.session.commit()
current_app.logger.info("Deleted asset '%s'." % asset_name)
return {}, 204
10 changes: 6 additions & 4 deletions flexmeasures/api/dev/tests/conftest.py
Expand Up @@ -6,17 +6,19 @@
from flexmeasures.data.models.time_series import Sensor


@pytest.fixture(scope="module", autouse=True)
def setup_api_test_data(db, setup_roles_users):
@pytest.fixture(scope="module")
def setup_api_test_data(db, setup_roles_users, setup_generic_assets):
"""
Set up data for API dev tests.
"""
print("Setting up data for API v2.0 tests on %s" % db.engine)
print("Setting up data for API dev tests on %s" % db.engine)
add_gas_sensor(db, setup_roles_users["Test Prosumer User 2"])


@pytest.fixture(scope="function")
def setup_api_fresh_test_data(fresh_db, setup_roles_users_fresh_db):
def setup_api_fresh_test_data(
fresh_db, setup_roles_users_fresh_db, setup_generic_assets_fresh_db
):
"""
Set up fresh data for API dev tests.
"""
Expand Down