Skip to content

Commit

Permalink
Merge b7f7b07 into 6b0e04b
Browse files Browse the repository at this point in the history
  • Loading branch information
Flix6x committed Jun 12, 2023
2 parents 6b0e04b + b7f7b07 commit b0be8a5
Show file tree
Hide file tree
Showing 17 changed files with 166 additions and 466 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/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Infrastructure / Support
* Introduction of the classes `Reporter`, `PandasReporter` and `AggregatorReporter` to help customize your own reporter functions (experimental) [see `PR #641 <https://www.github.com/FlexMeasures/flexmeasures/pull/641>`_ and `PR #712 <https://www.github.com/FlexMeasures/flexmeasures/pull/712>`_]
* The setting FLEXMEASURES_PLUGINS can be set as environment variable now (as a comma-separated list) [see `PR #660 <https://www.github.com/FlexMeasures/flexmeasures/pull/660>`_]
* Packaging was modernized to stop calling setup.py directly [see `PR #671 <https://www.github.com/FlexMeasures/flexmeasures/pull/671>`_]
* Remove API versions 1.0, 1.1, 1.2, 1.3 and 2.0, while allowing hosts to switch between ``HTTP status 410 (Gone)`` and ``HTTP status 404 (Not Found)`` responses [see `PR #667 <https://www.github.com/FlexMeasures/flexmeasures/pull/667>`_]
* Remove API versions 1.0, 1.1, 1.2, 1.3 and 2.0, while making sure that sunset endpoints keep returning ``HTTP status 410 (Gone)`` responses [see `PR #667 <https://www.github.com/FlexMeasures/flexmeasures/pull/667>`_ and `PR #717 <https://www.github.com/FlexMeasures/flexmeasures/pull/717>`_]
* Add code documentation from package structure and docstrings to official docs [see `PR #698 <https://www.github.com/FlexMeasures/flexmeasures/pull/698>`_]

.. warning:: The setting `FLEXMEASURES_PLUGIN_PATHS` has been deprecated since v0.7. It has now been sunset. Please replace it with :ref:`plugin-config`.
Expand Down
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 @@ -109,18 +109,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)
36 changes: 19 additions & 17 deletions flexmeasures/api/common/utils/deprecation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,40 @@

def sunset_blueprint(
blueprint,
api_version_sunset: str,
api_version_being_sunset: str,
sunset_link: str,
api_version_upgrade_to: str = "3.0",
blueprint_contents_removed: bool = True,
rollback_possible: bool = True,
**kwargs,
):
"""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 the sunset is inactive, this function will not affect any requests in this blueprint.
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():
def return_410_unless_host_rolls_back_sunrise():

# Override with custom info link, if set by host
_sunset_link = override_from_config(sunset_link, "FLEXMEASURES_API_SUNSET_LINK")
if (
rollback_possible
and not current_app.config["FLEXMEASURES_API_SUNSET_ACTIVE"]
):
# Sunset is inactive and blueprint contents should still be there,
# so we let the request pass to the endpoint implementation
pass
else:
# Override with custom info link, if set by host
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.",
f"API version {api_version_being_sunset} has been sunset. Please upgrade to API version {api_version_upgrade_to}. See {link} for more information.",
)
elif blueprint_contents_removed:
abort(404)
else:
# Sunset is inactive and blueprint contents are still there,
# so we let the request pass to the endpoint implementation
pass

blueprint.before_request(let_host_switch_to_returning_410)
blueprint.before_request(return_410_unless_host_rolls_back_sunrise)


def deprecate_fields(
Expand Down Expand Up @@ -128,6 +129,7 @@ def deprecate_blueprint(
deprecation_link: str | None = None,
sunset_date: pd.Timestamp | str | None = None,
sunset_link: str | None = None,
**kwargs,
):
"""Deprecates every route on a blueprint by adding the "Deprecation" header with a deprecation date.
Expand Down
77 changes: 77 additions & 0 deletions flexmeasures/api/sunset/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
A place to keep all routes to endpoints that previously existed and are now sunset.
"""

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_being_sunset="1.0",
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="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1.html",
),
dict(
blueprint=flexmeasures_api_v1_1,
api_version_being_sunset="1.1",
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="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_1.html",
),
dict(
blueprint=flexmeasures_api_v1_2,
api_version_being_sunset="1.2",
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="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_2.html",
),
dict(
blueprint=flexmeasures_api_v1_3,
api_version_being_sunset="1.3",
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="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v1_3.html",
),
dict(
blueprint=flexmeasures_api_v2_0,
api_version_being_sunset="2.0",
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="https://flexmeasures.readthedocs.io/en/v0.13.0/api/v2_0.html",
),
]

for info in SUNSET_INFO:
deprecate_blueprint(**info)
sunset_blueprint(**info, 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
34 changes: 0 additions & 34 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.

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

This file was deleted.

Loading

0 comments on commit b0be8a5

Please sign in to comment.