Skip to content

Commit

Permalink
Merge bf1ca58 into e4c77cc
Browse files Browse the repository at this point in the history
  • Loading branch information
Flix6x committed Jun 8, 2023
2 parents e4c77cc + bf1ca58 commit 8064ed6
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 442 deletions.
20 changes: 5 additions & 15 deletions documentation/api/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ When an API feature becomes obsolete, we deprecate it.
Deprecation of major features doesn't happen a lot, but when it does, it happens in multiple stages, during which we support clients and hosts in adapting.
For more information on our multi-stage deprecation approach and available options for FlexMeasures hosts, see :ref:`Deprecation and sunset for hosts<api_deprecation_hosts>`.

.. _api_deprecation_clients:

Clients
^^^^^^^

Expand Down Expand Up @@ -154,10 +156,9 @@ Hosts

FlexMeasures versions go through the following stages for deprecating major features (such as API versions):

- :ref:`api_deprecation_stage_1`: status 200 (OK) with relevant headers, plus a toggle to 410 (Gone) for blackout tests
- :ref:`api_deprecation_stage_1`: status 200 (OK) with :ref:`relevant headers<api_deprecation_clients>`, plus a toggle to 410 (Gone) for blackout tests
- :ref:`api_deprecation_stage_2`: status 410 (Gone), plus a toggle to 200 (OK) for sunset rollbacks
- :ref:`api_deprecation_stage_3`: status 404 (Not Found), plus a toggle to 410 (Gone) for removal rollbacks
- :ref:`api_deprecation_stage_4`: status 404 (Not Found), and removal of relevant endpoints
- :ref:`api_deprecation_stage_3`: status 410 (Gone)

Let's go over these stages in more detail.

Expand Down Expand Up @@ -202,15 +203,4 @@ To enable this, just set the config setting ``FLEXMEASURES_API_SUNSET_ACTIVE = F
Stage 3: Definitive sunset
""""""""""""""""""""""""""

After upgrading to one of the next FlexMeasures versions (e.g. ``flexmeasures==0.14``), clients that call sunset endpoints will receive ``HTTP status 404 (Not Found)`` responses.
In case you need clients to receive the slightly more informative ``HTTP status 410 (Gone)`` for a little while longer, we will continue to support a "removal rollback".
To enable this, just set the config setting ``FLEXMEASURES_API_SUNSET_ACTIVE = True``.
This, just like in deprecation stages 1 and 2, leads to status 410 (Gone) responses.
Note that ``FLEXMEASURES_API_SUNSET_ACTIVE = False`` now leads to status 404 (Not Found) responses, unlike in deprecation stages 1 and 2, where this would have lead to status 200 (OK) responses.

.. _api_deprecation_stage_4:

Stage 4: Removal
""""""""""""""""

After upgrading to one of the next FlexMeasures versions (e.g. ``flexmeasures==0.15``), clients that call sunset endpoints will receive ``HTTP status 404 (Not Found)`` responses.
After upgrading to one of the next FlexMeasures versions (e.g. ``flexmeasures==0.14``), clients that call sunset endpoints will receive ``HTTP status 410 (Gone)`` responses.
2 changes: 1 addition & 1 deletion documentation/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ FLEXMEASURES_API_SUNSET_ACTIVE

Allow control over the effect of sunsetting API versions.
Specifically, if True, the endpoints of sunset API versions will return ``HTTP status 410 (Gone)`` status codes.
If False, these endpoints will either return ``HTTP status 404 (Not Found) status codes``, or work like before (including Deprecation and Sunset headers in their response), depending on whether the installed FlexMeasures version still contains the endpoint implementations.
If False, these endpoints will either return ``HTTP status 410 (Gone) status codes``, or work like before (including Deprecation and Sunset headers in their response), depending on whether the installed FlexMeasures version still contains the endpoint implementations.

Default: ``False``

Expand Down
12 changes: 2 additions & 10 deletions flexmeasures/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,10 @@ def register_at(app: Flask):
play_register_at(app)

# Load all versions of the API functionality
from flexmeasures.api.v1 import register_at as v1_register_at
from flexmeasures.api.v1_1 import register_at as v1_1_register_at
from flexmeasures.api.v1_2 import register_at as v1_2_register_at
from flexmeasures.api.v1_3 import register_at as v1_3_register_at
from flexmeasures.api.v2_0 import register_at as v2_0_register_at
from flexmeasures.api.v3_0 import register_at as v3_0_register_at
from flexmeasures.api.dev import register_at as dev_register_at
from flexmeasures.api.sunset import register_at as sunset_register_at

v1_register_at(app)
v1_1_register_at(app)
v1_2_register_at(app)
v1_3_register_at(app)
v2_0_register_at(app)
v3_0_register_at(app)
dev_register_at(app)
sunset_register_at(app)
38 changes: 24 additions & 14 deletions flexmeasures/api/common/utils/deprecation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,47 @@
from flexmeasures.utils.time_utils import to_http_time


def abort_with_sunset_info(
api_version_sunset: str,
sunset_link: str,
api_version_upgrade_to: str = "3.0",
):
# Override with custom info link, if set by host
_sunset_link = override_from_config(sunset_link, "FLEXMEASURES_API_SUNSET_LINK")

abort(
410,
f"API version {api_version_sunset} has been sunset. Please upgrade to API version {api_version_upgrade_to}. See {_sunset_link} for more information.",
)


def sunset_blueprint(
blueprint,
api_version_sunset: str,
sunset_link: str,
api_version_upgrade_to: str = "3.0",
blueprint_contents_removed: bool = True,
rollback_possible: bool = True,
):
"""Sunsets every route on a blueprint by returning 410 (Gone) responses, if sunset is active.
Whether the sunset is active can be toggled using the config setting "FLEXMEASURES_API_SUNSET_ACTIVE".
If inactive, either:
- return 404 (Not Found) if the blueprint contents have been removed, or
- pass the request to be handled by the endpoint implementation.
If inactive, pass the request to be handled by the endpoint implementation.
If the endpoint implementations have been removed, set rollback_possible=False.
Errors will be logged by utils.error_utils.error_handling_router.
"""

def let_host_switch_to_returning_410():

# Override with custom info link, if set by host
_sunset_link = override_from_config(sunset_link, "FLEXMEASURES_API_SUNSET_LINK")

if current_app.config["FLEXMEASURES_API_SUNSET_ACTIVE"]:
abort(
410,
f"API version {api_version_sunset} has been sunset. Please upgrade to API version {api_version_upgrade_to}. See {_sunset_link} for more information.",
if (
not rollback_possible
or current_app.config["FLEXMEASURES_API_SUNSET_ACTIVE"]
):
abort_with_sunset_info(
api_version_sunset, sunset_link, api_version_upgrade_to
)
elif blueprint_contents_removed:
abort(404)
else:
# Sunset is inactive and blueprint contents are still there,
# Sunset is inactive and blueprint contents should still be there,
# so we let the request pass to the endpoint implementation
pass

Expand Down
69 changes: 69 additions & 0 deletions flexmeasures/api/sunset/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from flask import Flask, Blueprint

from flexmeasures.api.common.utils.deprecation_utils import (
deprecate_blueprint,
sunset_blueprint,
)


# The sunset API blueprints. They are registered with the Flask app (see register_at)
flexmeasures_api_v1 = Blueprint("flexmeasures_api_v1", __name__)
flexmeasures_api_v1_1 = Blueprint("flexmeasures_api_v1_1", __name__)
flexmeasures_api_v1_2 = Blueprint("flexmeasures_api_v1_2", __name__)
flexmeasures_api_v1_3 = Blueprint("flexmeasures_api_v1_3", __name__)
flexmeasures_api_v2_0 = Blueprint("flexmeasures_api_v2_0", __name__)

SUNSET_INFO = [
dict(
blueprint=flexmeasures_api_v1,
api_version_sunset="1.0",
sunset_link="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1.html",
),
dict(
blueprint=flexmeasures_api_v1_1,
api_version_sunset="1.1",
sunset_link="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_1.html",
),
dict(
blueprint=flexmeasures_api_v1_2,
api_version_sunset="1.2",
sunset_link="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_2.html",
),
dict(
blueprint=flexmeasures_api_v1_3,
api_version_sunset="1.3",
sunset_link="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_3.html",
),
dict(
blueprint=flexmeasures_api_v2_0,
api_version_sunset="2.0",
sunset_link="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v2_0.html",
),
]

for info in SUNSET_INFO:
deprecate_blueprint(
blueprint=info["blueprint"],
deprecation_date="2022-12-14",
deprecation_link="https://flexmeasures.readthedocs.io/en/latest/api/introduction.html#deprecation-and-sunset",
sunset_date="2023-05-01",
sunset_link=info["sunset_link"],
)
sunset_blueprint(
blueprint=info["blueprint"],
api_version_sunset=info["api_version_sunset"],
sunset_link=info["sunset_link"],
rollback_possible=False,
)


def register_at(app: Flask):
"""This can be used to register this blueprint together with other api-related things"""

import flexmeasures.api.sunset.routes # noqa: F401 this is necessary to load the endpoints

app.register_blueprint(flexmeasures_api_v1, url_prefix="/api/v1")
app.register_blueprint(flexmeasures_api_v1_1, url_prefix="/api/v1_1")
app.register_blueprint(flexmeasures_api_v1_2, url_prefix="/api/v1_2")
app.register_blueprint(flexmeasures_api_v1_3, url_prefix="/api/v1_3")
app.register_blueprint(flexmeasures_api_v2_0, url_prefix="/api/v2_0")
61 changes: 61 additions & 0 deletions flexmeasures/api/sunset/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from flexmeasures.api.sunset import (
flexmeasures_api_v1,
flexmeasures_api_v1_1,
flexmeasures_api_v1_2,
flexmeasures_api_v1_3,
flexmeasures_api_v2_0,
)


@flexmeasures_api_v1.route("/getMeterData", methods=["GET", "POST"])
@flexmeasures_api_v1.route("/postMeterData", methods=["POST"])
@flexmeasures_api_v1.route("/getService", methods=["GET"])
@flexmeasures_api_v1_1.route("/getConnection", methods=["GET"])
@flexmeasures_api_v1_1.route("/postPriceData", methods=["POST"])
@flexmeasures_api_v1_1.route("/postWeatherData", methods=["POST"])
@flexmeasures_api_v1_1.route("/getPrognosis", methods=["GET"])
@flexmeasures_api_v1_1.route("/postPrognosis", methods=["POST"])
@flexmeasures_api_v1_1.route("/getMeterData", methods=["GET"])
@flexmeasures_api_v1_1.route("/postMeterData", methods=["POST"])
@flexmeasures_api_v1_1.route("/getService", methods=["GET"])
@flexmeasures_api_v1_2.route("/getDeviceMessage", methods=["GET"])
@flexmeasures_api_v1_2.route("/postUdiEvent", methods=["POST"])
@flexmeasures_api_v1_2.route("/getConnection", methods=["GET"])
@flexmeasures_api_v1_2.route("/postPriceData", methods=["POST"])
@flexmeasures_api_v1_2.route("/postWeatherData", methods=["POST"])
@flexmeasures_api_v1_2.route("/getPrognosis", methods=["GET"])
@flexmeasures_api_v1_2.route("/getMeterData", methods=["GET"])
@flexmeasures_api_v1_2.route("/postMeterData", methods=["POST"])
@flexmeasures_api_v1_2.route("/postPrognosis", methods=["POST"])
@flexmeasures_api_v1_2.route("/getService", methods=["GET"])
@flexmeasures_api_v1_3.route("/getDeviceMessage", methods=["GET"])
@flexmeasures_api_v1_3.route("/postUdiEvent", methods=["POST"])
@flexmeasures_api_v1_3.route("/getConnection", methods=["GET"])
@flexmeasures_api_v1_3.route("/postPriceData", methods=["POST"])
@flexmeasures_api_v1_3.route("/postWeatherData", methods=["POST"])
@flexmeasures_api_v1_3.route("/getPrognosis", methods=["GET"])
@flexmeasures_api_v1_3.route("/getMeterData", methods=["GET"])
@flexmeasures_api_v1_3.route("/postMeterData", methods=["POST"])
@flexmeasures_api_v1_3.route("/postPrognosis", methods=["POST"])
@flexmeasures_api_v1_3.route("/getService", methods=["GET"])
@flexmeasures_api_v2_0.route("/assets", methods=["GET"])
@flexmeasures_api_v2_0.route("/assets", methods=["POST"])
@flexmeasures_api_v2_0.route("/asset/<id>", methods=["GET"])
@flexmeasures_api_v2_0.route("/asset/<id>", methods=["PATCH"])
@flexmeasures_api_v2_0.route("/asset/<id>", methods=["DELETE"])
@flexmeasures_api_v2_0.route("/users", methods=["GET"])
@flexmeasures_api_v2_0.route("/user/<id>", methods=["GET"])
@flexmeasures_api_v2_0.route("/user/<id>", methods=["PATCH"])
@flexmeasures_api_v2_0.route("/user/<id>/password-reset", methods=["PATCH"])
@flexmeasures_api_v2_0.route("/getConnection", methods=["GET"])
@flexmeasures_api_v2_0.route("/postPriceData", methods=["POST"])
@flexmeasures_api_v2_0.route("/postWeatherData", methods=["POST"])
@flexmeasures_api_v2_0.route("/getPrognosis", methods=["GET"])
@flexmeasures_api_v2_0.route("/getMeterData", methods=["GET"])
@flexmeasures_api_v2_0.route("/postMeterData", methods=["POST"])
@flexmeasures_api_v2_0.route("/postPrognosis", methods=["POST"])
@flexmeasures_api_v2_0.route("/getService", methods=["GET"])
@flexmeasures_api_v2_0.route("/getDeviceMessage", methods=["GET"])
@flexmeasures_api_v2_0.route("/postUdiEvent", methods=["POST"])
def implementation_gone():
pass
30 changes: 0 additions & 30 deletions flexmeasures/api/v1/__init__.py

This file was deleted.

16 changes: 0 additions & 16 deletions flexmeasures/api/v1/routes.py

This file was deleted.

29 changes: 0 additions & 29 deletions flexmeasures/api/v1_1/__init__.py

This file was deleted.

41 changes: 0 additions & 41 deletions flexmeasures/api/v1_1/routes.py

This file was deleted.

Loading

0 comments on commit 8064ed6

Please sign in to comment.