Skip to content

Remove Python 2 and Python 3.5 support #3457

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

Closed
pekkaklarck opened this issue Feb 7, 2020 · 17 comments
Closed

Remove Python 2 and Python 3.5 support #3457

pekkaklarck opened this issue Feb 7, 2020 · 17 comments

Comments

@pekkaklarck
Copy link
Member

pekkaklarck commented Feb 7, 2020

Python 2 was officially retired in April 2020 and even security fixes won't be released afterwards. Robot Framework continuing to support it much long does not make sense, and the Robot Framework Foundation has decided that it will not sponsor Robot Framework development targeting Python 2 anymore in 2021. The main reason we don't want to be more aggressive removing Python 2 support is that it would also drop Jython support.

When Python 2 support is removed, also support for Python 3.5 and older will be removed. This eases development by making it possible to take into use newer Python features, most notably f-strings. Python 3.5 will also reach its end-of-life in September 2020, a lot before the expected Robot Framework 5.0 release.

The current Python version support plan is as follows:

  • Robot Framework 3.2 supports Python 2.7 as well as Python 3.4 and newer. This covers also all RF 3.2.x bug fix releases.

  • Robot Framework 4.0 planned for late 2020 still supports Python 2.7 as well as Python 3.5 and newer. This covers also RF 4.0.x bug fix releases and possible RF 4.x feature releases.

  • Robot Framework 5.0 will support only Python 3.6 and newer.

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Feb 7, 2020

The decision to make the version where Python 2 support is removed Robot Framework 4.0 means that there most likely won't be Robot Framework 3.3 relase. Such a release may be done if there are needs for changes before RF 4.0 that are too big for RF 3.2.x minor releases. This could, for example, include enhancements to the new parser to make development of external tools (e.g. LSP) easier. Possible RF 3.3 would nevertheless be a pretty small releases and all big enhancements (IF/ELSE, nested loops, etc.) have been moved from RF 3.3 to RF 4.0.

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Feb 7, 2020

For projects using Robot Framework transitioning from Python 2 to Python 3 is likely to require some work. Luckily most of the important Robot Framework libraries and tools have supported Python 3 for some time, so for projects using only ready-made libraries and tools upgrading shouldn't be too hard.

If projects have their own libraries, they obviously need to be ported to Python 3 as well. With smallish projects it may be possible to just go directly from Python 2 to Python 3, but with bigger ones it is likely that at leas some of the tooling needs to work with both versions for some time.

There are lot of references related to porting from Python 2 to Python 3 online. Probably the best starting point is the official Porting Python 2 Code to Python 3 guide. For even more information there is, for example, the free Supporting Python 3: An in-depth guide online book but its already few years old. Please add references to good porting guides and other tips and tricks to comments!

@pekkaklarck
Copy link
Member Author

The biggest problem with dropping Python 2 support is that neither Jython nor IronPython are compatible with Python 3. Neither of them have concrete plans when Python 3 compatible release would be ready and we cannot wait for them indefinitely. In 2021 we must move forward.

In the .NET domain the situation isn't that bad because there is already an alternative to use the pythonnet module instead of IronPython. This module makes it possible for the "normal" Python to communicating with the .NET runtime seamlessly. Because it works with Python 2 and Python 3, it is the recommended way forward for the IronPython users.

Unfortunately in the JVM domain there is no such clear alternative to Jython. The Graal Python project looks promising, but is apparently still far from being stable. As the last resort it is always possible to use the Remote library interface.

@pekkaklarck
Copy link
Member Author

There have been questions why we don't drop Python 2 support sooner. There are several reasons:

  • A big portion of the community still uses Python 2. I'd like to give them a bit more time to upgrade to Python 3.
  • Jython users don't have a clear upgrade path. This is the most important reason.
  • Some Python 2 users may be in situation where upgrading doesn't make sense. The project might be, for example, in maintenance mode or ending soon.
  • RF 4.0 is going to contain SKIP status (New SKIP status #3622) which is a big change making the framework a lot better. I want users who cannot upgrade to Python 3 for whatever reason to be able to benefit from that.
  • Unless we also drop Python 3.5 support, we cannot really benefit from new Python features.
  • We have lot of utilities etc. to make it easy to support Python 2. The extra work in RF 4.0 due to Python 2 support is relatively small.
  • Making the code base Python 3 only is itself a rather big task and there's also lot of documentation that needs to be updated. We have pretty tight schedule with RF 4.0 and I'm not sure we'd have time for that.

pekkaklarck added a commit that referenced this issue Sep 23, 2021
Removed Python 2/Jython/IronPython tests from files that are still
otherwise valid will be committed separately.

First bigger part of #3457.
pekkaklarck added a commit that referenced this issue Sep 23, 2021
Test files only testing these not anymore supported interpreters,
including libraries, were removed already in a separate commit.

Part of #3457.

Also some whitespace cleanup here and there.
pekkaklarck added a commit that referenced this issue Oct 14, 2021
Removed Python 2/Jython/IronPython tests from files that are still
otherwise valid will be committed separately.

First bigger part of #3457.
pekkaklarck added a commit that referenced this issue Oct 14, 2021
Test files only testing these not anymore supported interpreters,
including libraries, were removed already in a separate commit.

Part of #3457.

Also some whitespace cleanup here and there.
pekkaklarck added a commit that referenced this issue Oct 15, 2021
Removed Python 2/Jython/IronPython tests from files that are still
otherwise valid will be committed separately.

First bigger part of #3457.
pekkaklarck added a commit that referenced this issue Oct 15, 2021
Test files only testing these not anymore supported interpreters,
including libraries, were removed already in a separate commit.

Part of #3457.

Also some whitespace cleanup here and there.
pekkaklarck added a commit that referenced this issue Oct 16, 2021
This commit tries to remove all code related to Python 2, Jython and
IronPython. Some code that's not anymore needed (e.g. unnecessary
inheritance of `object`) was still left and there may be other
traces as well.
@pekkaklarck
Copy link
Member Author

This work is ongoing in the bye-bye-python-2 branch. Tests and their support code was removed first and the latest commit tried to remove all Python 2, Jython and IronPython related code. That branch is about to be ready to be merged, but I want to wait at least until Monday to see are there bug reports related to the new RF 4.1.2 release that would require immediate RF 4.1.3.

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Oct 16, 2021

There's a lot of work still to be done related to this:

  • Remove unnecessary object inheritance.
  • Use isintance instead of is_unicode/is_string/is_number/etc. that directly use isinstance internally. These are so widely used that removing an extra function call can affect performance.
  • Check do comments, docstrings etc. refer to not-supported versions and update as needed.
  • Update installation instructions.
  • Check the User Guide.
  • Decide should we leave parts of our Python 2/3 compatibility layer in place. We don't need it anymore, but there may be external libraries using PY2, is_string, etc. Keeping them in robot.utils isn't a big problem.
  • Consider converting sting formatting to use f-strings. This may not be worth the effort though. A deciding factor is whether f-strings are faster to process by Python than % formatting.
  • Add type hints to public APIs where feasible. Can be done after closing this issue and in some cases these additions should probably get their own issues.

@hugovk
Copy link
Contributor

hugovk commented Oct 16, 2021

  • Remove unnecessary object inheritance.
  • Consider converting sting formatting to use f-strings. This may not be worth the effort though. A deciding factor is whether f-strings are faster to process by Python than % formatting.

python3 -m pip install -U pyupgrade && pyupgrade **/*.py --py36-plus will take care of a lot of the object inheritance removals and f-string conversion, plus a lot more.

https://github.com/asottile/pyupgrade/

f-strings are faster: https://www.scivision.dev/python-f-string-speed/

  • Decide should we leave parts of our Python 2/3 compatibility layer in place. We don't need it anymore, but there may be external libraries using PY2, is_string, etc. Keeping them in robot.utils isn't a big problem.

The Python 2/3.5 drop will be released in a major version Robot Framework 5.0, so breaking changes are allowed with SemVer. And I think it's fair enough to drop Python 2 compatibility functions too, especially trivial ones like is_string that can be re-defined in external libraries.

@pekkaklarck
Copy link
Member Author

I already removed object inheritance using sed. Need to see what else pyupgrade would do and good to know f-strings are faster.

The reason I think we should leave most important parts of the Python 2/3 compatibility layer is that there can be external libraries that use them but aren't actively maintained. Breaking such libraries in RF 5.0 doesn't help us getting people to migrate and leaving things like PY2, PY3 = False, True in robot.utils doesn't really cost us anything.

@pekkaklarck
Copy link
Member Author

The bye-bye-python-2 branch has been merged. This issue cannot be closed until documentation has been updated, but otherwise things listed above don't have high priority.

@pekkaklarck
Copy link
Member Author

I've been thinking about our Python 2/3 compatibility layer and believe we should preserve most important parts of it. The reason is that there are several tools and libraries that are Python 3 compatible and use this layer to provide also Python 2 compatibility. If we remove the compatibility layer, all these tools and libraries will be incompatible with RF 5 which isn't great. The fact that these tools/libs themselves are Python 3 compatible doesn't affect this.

The API we'd preserve would be pretty dummy and would itself naturally work only with Python 3. We obviously cannot preserve it indefinitely, but I believe we should wait at least until RF 5.2 before removing it. I don't think there's a need to preserve the whole compatibility layer as some of the utils are very rarely used and some were just needed to workaround bugs in old Jython and IronPython versions.

This is what I think we should preserve:

  • PY2, PY3, JYTHON, IRONPYTHON and JAVA_VERSION constants
  • py2to3 and py3to2 class decorators
  • is_unicode for checking is something a Unicode string (could be an alias for is_string)
  • unicode for checking against the unicode type (could be an alias for str)
  • unic for safely converting values to Unicode (we still have this util and may need it in the future as well, should anyway be renamed so compatibility alias would still be needed)

These are likely not used a lot or at all but can be considered:

  • with_metaclass for applying a metaclass
  • StringIO (io.StringIO works in most cases also with Python 2)
  • Mapping and MutableMapping (available in collections until Python 3.10)
  • unwrap for unwrapping decorators (available in inspect in Python 3)
  • is_java_init and is_java_method for detecting Java based methods

@hugovk
Copy link
Contributor

hugovk commented Nov 22, 2021

A reasonable approach, and I recommend emitting DeprecationWarnings where possible, they really helps people know it's time to stop using things.

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Nov 22, 2021

Deprecation warnings would be great but make this more complicated. I was thinking we could just use something like

PY3 = True
PY2 = JYTHON = IRONPYTHON = False
is_unicode = is_string

and adding warnings there is some extra work. I'd say above is fine at least in RF 5.0 and we can then consider warnings later. Or we just keep this until RF 6. The above ands no maintenance costs nor causes any other problems.

@hugovk
Copy link
Contributor

hugovk commented Nov 22, 2021

Yep, the problems don't come during refactoring, they come when things are removed with no warnings.

(We removed a constant in Pillow after only deprecating in docs, and the disruption it caused meant we had to re-add it and emit warnings for another cycle.)

It's a bit Catch-22: if people are not using them they might as well be removed right now; but if people are using them, they should be warned first :)

For things like constants it is a bit tricky, but at least for functions it's as simple as:

 def is_unicode(item):
+    warnings.warn("TODO", DeprecationWarning)
     return isinstance(item, str)

(And optionally:)

 def is_unicode(item):
-    return isinstance(item, str)
+    warnings.warn("TODO", DeprecationWarning)
+    return is_string(item)

pekkaklarck added a commit that referenced this issue Nov 24, 2021
No difference anymore with Python 3. #3457
@pekkaklarck
Copy link
Member Author

I submitted separate issue #4150 about preserving but deprecating the Python 2/3 compatibility layer. Utils discussed above are now added back and others can be added later if needed. Any comments related to this belong to the new issue, not here.

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Nov 24, 2021

You are @hugovk right that loudly deprecating functions is easy. Constants are harder but something like this ought to work:

>>> import warnings
>>> class DeprecatedFlag:
...     def __init__(self, name, value):
...         self.name = name
...         self.value = value
...     def __bool__(self):
...         warnings.warn(f'{self.name} is deprecated.')
...         return self.value
... 
>>> PY2 = DeprecatedFlag('PY2', False)
>>> if PY2:
...     1/0
... 
<stdin>:6: UserWarning: PY2 is deprecated.

I believe it's anyway best to not loudly deprecate anything in RF 5.0 but that can be added in RF 5.1 or 5.2. Anyway, #4150 is better place to discuss this more.

Notice that the above solution doesn't work if someone has used PY2 is False or PY2 == False. The latter we could easily support by implementing also __eq__, but the former is AFAIK impossible. This kind of Boolean flags ought to be used like if PY2 so probably this solution would be fine.

@pekkaklarck
Copy link
Member Author

This issue is done otherwise but documentation in the User Guide as well as installation instructions still need to be updated.

@pekkaklarck
Copy link
Member Author

We now have separate issues for updating the User Guide, installation instructions, and README. This issue can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants