You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
One of the biggest challenges to improving software may be dealing with breaking changes. Sometimes decisions and code features are made that are not forward thinking enough and preclude other enhancements to be made.
Historically, Sanic has dealt with this in several ways:
Utilizing an LTS schedule. Having a yearly long-term support release means that organizations and developers can restrict their upgrades to a yearly cycle. This provides applications more stability and provides the framework more freedom to move quickly.
Requiring a two-cycle deprecation. When the Sanic Core Developers decide that a breaking change is needed, generally this happens in context of the stated deprecation policy. This policy states that a breaking change can be made so long as there remains two release cycles before the old feature is removed, and during those two releases there are deprecation notices to the developers. This generally has take place in Release Notes and in the Changelog.
The Problem
This policy is great when there can be an easy transition period. That means to say when both the old and the new can be supported side-by-side. This is not always the case. Sometimes breaking changes with no notice must be made.
The question then becomes how can we ease this issue for developers and not break existing applications?
While discussing one such breaking change with @Tronic, he suggested we offer a compatibility mode. That is to say that there is some method that we allow developers to retain an old feature, but still allow them to upgrade and take advantage of newer features. This could be achievable with an early defined global variable in the target application.
An early defined global variable could be caught very early at import time in Sanic, and therefore used to alter import paths. If you look at the POC branch linked above, you will see something this in __version__.py:
... Or, it can be used to make other decisions early on in the startup process before any other Sanic objects are loaded into memory. By placing the value in sanic.__version__, there should not be any circular import issues.
Important considerations
One of the larger considerations is making sure that an sort of compatibility would work seemlessly for the developer. That means that IDEs should still function as expected, etc. As you can see in the below screenshot, this does work with VS Code.
VS Code is able to follow the dynamic import path to get to the "new" Request that has a something_new property and auto-suggest it. 👍
However, at least with the proposed solution, this does not work for mypy. I did not test with any other type checker. Perhaps there is a nicer implementation than the POC branch that will allow for both IDE and type checkers.
Requirements for a "go forward solution"
Configuration as early as possible so Sanic can make decisions before imports start happening
Allow developers to opt in and out of a set of defined features. The current POC branch contemplates "all or nothing" approach. It is conceivable that there may be some way to allow per-feature choices
IDEs must be able to follow any signature changes to make it easy for developers
Type checkers must be able to follow any signature changes to make it easy for developers
Question to be answered
The POC should not be taken as a proposed solution. Rather, it should be noted it is only meant to be illustrative of the problem and potential ways to solve it. What needs to be decided here first and foremost:
Should Sanic offer a mechanism for allowing breaking changes to occur, but developers retain a compatibility mode?
Some reasons why "yes"
There exists a set of changes that cannot be made without breaking APIs.
Accessors may be added, functional signatures changed, etc.
Developers may have some more freedom to access some features they might not otherwise be able to.
Core development can be more free to make breaking changes
Some reasons why "not"
Additional code complexity and maintenance
There already exists a strategy for "locking in" a feature set: LTS. Perhaps we need to be more up front about this and encourage developers to use it more often.
Breaking changes are a problem for a reason. If we really need it, perhaps a more "creative" approach should be taken: adding a new method like Sanic.serve_legacy. Using new methods and types can largely achieve the same result.
Additional context
No response
Is this a breaking change?
Yes
The text was updated successfully, but these errors were encountered:
classWidget:
deflegacy_do_something(self, thing_1, **kwargs):
if__compatibility__=="<OLD VERSION>":
deprecation("This is old and will be removed. Upgrade to future_do_something")
deffuture_do_something(self, thing_1, thing_2, thing_3):
...
do_something=legacy_do_something
Developers that did nothing would have the same behavior with an annoying warning. They can silence the warning by setting a value somewhere. We do allow for the stdlib filterwarnings, but that works against ALL Sanic deprecation warnings. What I am suggesting here is that some warnings be compat version specific.
A future step could then move to something like this:
Link to code
main...breaking-change-compat-mode
Proposal
Background
One of the biggest challenges to improving software may be dealing with breaking changes. Sometimes decisions and code features are made that are not forward thinking enough and preclude other enhancements to be made.
Historically, Sanic has dealt with this in several ways:
The Problem
This policy is great when there can be an easy transition period. That means to say when both the old and the new can be supported side-by-side. This is not always the case. Sometimes breaking changes with no notice must be made.
The question then becomes how can we ease this issue for developers and not break existing applications?
A Solution
There is a proof of concept branch with change comparison linked above
While discussing one such breaking change with @Tronic, he suggested we offer a compatibility mode. That is to say that there is some method that we allow developers to retain an old feature, but still allow them to upgrade and take advantage of newer features. This could be achievable with an early defined global variable in the target application.
An early defined global variable could be caught very early at import time in Sanic, and therefore used to alter import paths. If you look at the POC branch linked above, you will see something this in
__version__.py
:What it is doing is inspecting the call stack looking for that variable
__SANIC_COMPATIBILITY__
. That value then can be used to dynamically import ...... Or, it can be used to make other decisions early on in the startup process before any other Sanic objects are loaded into memory. By placing the value in
sanic.__version__
, there should not be any circular import issues.Important considerations
One of the larger considerations is making sure that an sort of compatibility would work seemlessly for the developer. That means that IDEs should still function as expected, etc. As you can see in the below screenshot, this does work with VS Code.
VS Code is able to follow the dynamic import path to get to the "new"
Request
that has asomething_new
property and auto-suggest it. 👍However, at least with the proposed solution, this does not work for
mypy
. I did not test with any other type checker. Perhaps there is a nicer implementation than the POC branch that will allow for both IDE and type checkers.Requirements for a "go forward solution"
Question to be answered
The POC should not be taken as a proposed solution. Rather, it should be noted it is only meant to be illustrative of the problem and potential ways to solve it. What needs to be decided here first and foremost:
Should Sanic offer a mechanism for allowing breaking changes to occur, but developers retain a compatibility mode?
Some reasons why "yes"
Some reasons why "not"
Sanic.serve_legacy
. Using new methods and types can largely achieve the same result.Additional context
No response
Is this a breaking change?
The text was updated successfully, but these errors were encountered: