Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Python 3.14 support #5352

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

Open
pekkaklarck opened this issue Mar 4, 2025 · 9 comments
Open

Python 3.14 support #5352

pekkaklarck opened this issue Mar 4, 2025 · 9 comments

Comments

@pekkaklarck
Copy link
Member

pekkaklarck commented Mar 4, 2025

Python 3.14 will be released in October 2025, but we should make sure Robot Framework is compatible with it already now.

Probably the biggest change in Python 3.14, and certainly the change that can affect Robot Framework the most, is deferred evaluation of annotations (PEP 649). Robot uses annotations for type conversion and such runtime annotation usage isn't that typical.

@pekkaklarck pekkaklarck added this to the v7.3 milestone Mar 4, 2025
@pekkaklarck
Copy link
Member Author

I tested Python 3.14 alpha 5 and all type conversion related tests succeeded. I don't know the exact status of PEP 649, but at least the current changes don't seem to affect us.

@pekkaklarck
Copy link
Member Author

The only tests that failed were using the jsonschema module for validating JSON outputs by Libdoc and Robot. The reason is that this module doesn't have Python 3.14 compatible releases available and compiling the Rust code it uses failed:
python-jsonschema/jsonschema#1337

We needed to wait for Python 3.13 compatible jsonschema version for quite a long time as well. At the moment missing jsonschema module cases problems also with unrelated tests, but I (party) fixed that locally and was able to run tests. I probably should fix this issue properly and also allow excluding tests needing jsonschema with --exclude require-jsonschema.

pekkaklarck added a commit that referenced this issue Mar 6, 2025
- Acceptance test libraries gracefully handle the module not being
  available. Schema validation will obviously fail, but otherwise
  libraries work normally.

- Acceptance tests needing jsonschema got a new `require-jsonschema`
  tag that can be used for skipping or excluding them.

- Unit tests needing jsonschema are skipped if the module isn't
  installed.

The motivation with these changes is making it possible to test Robot
on Python 3.14 that isn't currently supported by jsonschema. (#5352)
@fedepell
Copy link
Contributor

Attaching to this for Fedora 43 we are testing with Python 3.14 and with a6 I see some tests failing and are all around Unions. I see from the changelogs of Python that stuff was indeed changed around unions, but my understanding of the details is sadly not right now good enough to be of concrete help.

Output of failed tests:

ERROR: test_empty_union_not_allowed
(test_typeinfo.TestTypeInfo.test_empty_union_not_allowed)
----------------------------------------------------------------------
Traceback (most recent call last):
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/running/test_typeinfo.py",
line 82, in test_empty_union_not_allowed
    assert_raises_with_msg(
    ~~~~~~~~~~~~~~~~~~~~~~^
        DataError, 'Union cannot be empty.',
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        TypeInfo.from_type_hint, union
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/asserts.py",
line 167, in assert_raises_with_msg
    callable_obj(*args, **kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/running/arguments/typeinfo.py",
line 194, in from_type_hint
    elif has_args(hint):
         ~~~~~~~~^^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/robottypes.py",
line 142, in has_args
    return bool(args and not all(isinstance(a, TypeVar) for a in args))
                                                                 ^^^^
TypeError: 'member_descriptor' object is not iterable

======================================================================
ERROR: test_union (test_robottypes.TestTypeRepr.test_union)
----------------------------------------------------------------------
Traceback (most recent call last):
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/utils/test_robottypes.py",
line 210, in test_union
    assert_equal(type_repr(Union), 'Union')
                 ~~~~~~~~~^^^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/robottypes.py",
line 116, in type_repr
    if nested and has_args(typ):
                  ~~~~~~~~^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/robottypes.py",
line 142, in has_args
    return bool(args and not all(isinstance(a, TypeVar) for a in args))
                                                                 ^^^^
TypeError: 'member_descriptor' object is not iterable

======================================================================
FAIL: test_typing (test_robottypes.TestTypeName.test_typing)
----------------------------------------------------------------------
Traceback (most recent call last):
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/utils/test_robottypes.py",
line 163, in test_typing
    assert_equal(type_name(item), exp)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/asserts.py",
line 181, in assert_equal
    _report_inequality(first, second, '!=', msg, values, formatter)
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File
"/builddir/build/BUILD/python-robotframework-7.1.1-build/robotframework-7.1.1/utest/../src/robot/utils/asserts.py",
line 230, in _report_inequality
    raise AssertionError(msg)
AssertionError: getset_descriptor != Union

I did a few tests by hand on the shell around what tests execute that could hopefully give some more inputs for you:

>>> import robot.utils
>>> from robot.utils import robottypes
>>> from typing import Literal, Union, TypedDict, TypeVar
>>> from typing import Any, Dict, List, Literal, Optional

>>> robottypes.type_repr(Union)
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    robottypes.type_repr(Union)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^
  File "/builddir/build/BUILD/python-robotframework-7.2.2-build/BUILDROOT/usr/lib/python3.14/site-packages/robot/utils/robottypes.py", line 116, in type_repr
    if nested and has_args(typ):
                  ~~~~~~~~^^^^^
  File "/builddir/build/BUILD/python-robotframework-7.2.2-build/BUILDROOT/usr/lib/python3.14/site-packages/robot/utils/robottypes.py", line 142, in has_args
    return bool(args and not all(isinstance(a, TypeVar) for a in args))
                                                                 ^^^^
TypeError: 'member_descriptor' object is not iterable

>>> robottypes.type_name(Union[int, float])
'Union'

>>> robottypes.type_name(Union)
'getset_descriptor'

>>> robottypes.type_name(Optional[int])
'Union'

If I can be of any help testing or trying something please just let me know!

@pekkaklarck
Copy link
Member Author

Thanks for the info! I need to test with a6 myself.

Is it easy to get Python preview releases installed on Fedora? I'm currently using Mint/Ubuntu with DeadSnakes, but could install Fedora to a virtual machine to test it out. I need a new laptop soon and although I've been happy with Mint, it would be interesting to look for alternatives.

@fedepell
Copy link
Contributor

There is a very active team testing Python upcoming versions and what is nice is that you can test those using the "mock" tool which basically creates a chroot with a set of packages (in this case Python preview but could be anything) where you can easily experiment without really installing on your system (and possibly messing up ;) ). In this case then you tell mock to use their previews packages and you get the environment immediately. (just to give a reference, here: https://copr.fedorainfracloud.org/coprs/g/python/python3.14/ )

So I'd say: yeah it is pretty easy and comfortable to generate roots with different versions (or combinations of) with their standard tooling! (I don't know Mint/Ubuntu + DeadSnakes tho to be able to compare if it would be easier or not)

@pekkaklarck
Copy link
Member Author

pekkaklarck commented Mar 19, 2025

I was able to reproduce the problem. I explain why it occurs and how it can be fixed below.

Previously the Union type (actually a special form rather than a type) didn't itself have __args__ or __origin__:

>>> from typing import Union
>>> Union.__args__
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    Union.__args__
  File "/usr/lib/python3.13/typing.py", line 548, in __getattr__
    raise AttributeError(item)
AttributeError: __args__. Did you mean: '__ror__'?
>>> Union.__origin__
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    Union.__origin__
  File "/usr/lib/python3.13/typing.py", line 548, in __getattr__
    raise AttributeError(item)
AttributeError: __origin__
>>> 

Now in Python 3.14 alpha 6 they exist but aren't really usable:

>>> from typing import Union
>>> Union.__args__
<member '__args__' of 'typing.Union' objects>
>>> list(Union.__args__)
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    list(Union.__args__)
    ~~~~^^^^^^^^^^^^^^^^
TypeError: 'member_descriptor' object is not iterable
>>> Union.__origin__
<attribute '__origin__' of 'typing.Union' objects>
>>> type(Union.__origin__)
<class 'getset_descriptor'>

The problem can be avoided by using typing.get_args and typing.get_origin instead of accessing __args__ and __origin__ directly as we currently do. This works the same way with Python 3.8 and newer:

>>> from typing import get_args, get_origin, Union
>>> get_args(Union)
()
>>> get_origin(Union) is None
True

I guess the reason we haven't consistently used get_args and get_origin is that they are new in Python 3.8 and this code was written when we supported also earlier versions. Anyway, using these functions is a good idea in general and allows removing some workaround code related to issues accessing __args__ directly.

It's somewhat strange that Union nowadays has __args__ and __origin__. I guess that's related to the merging of typing.Union and types.UnionType that's done in Python 3.14:

>>> from types import UnionType
>>> from typing import Union
>>> UnionType is Union
True

In earlier versions Union and UnionType were different objects. UnionType had __args__ already then but no __origin__:

>>> from types import UnionType
>>> UnionType.__args__
<member '__args__' of 'types.UnionType' objects>
>>> list(UnionType.__args__)
Traceback (most recent call last):
  File "<python-input-7>", line 1, in <module>
    list(UnionType.__args__)
    ~~~~^^^^^^^^^^^^^^^^^^^^
TypeError: 'member_descriptor' object is not iterable
>>> UnionType.__origin__
Traceback (most recent call last):
  File "<python-input-8>", line 1, in <module>
    UnionType.__origin__
AttributeError: type object 'types.UnionType' has no attribute '__origin__'

I'm not sure could Union (and UnionType) nowadays having __args__ and __origin__ be considered a regression. It could be argued that they are implementation details and get_args and get_origin should be used instead of accessing them directly. If you think it's a regression, please submit an issue to Python's issue tracker. I think I'm fine just using get_args and get_origin.

pekkaklarck added a commit that referenced this issue Mar 19, 2025
Using `typing.get_args(x)` and `typing.get_origin(x)` is simpler than
using `getattr(x, '__args__', None)` and `getattr(x, '__origin__', None)`.
It also avoids problems in some corner cases. Most mportantly, it
avoids issues with `Union` containing unusable `__args__` and
`__origin__` in Python 3.14 alpha 6 (#5352).

`get_args` (new in Python 3.8) also makes our `has_args` redundant
and it is deprecated.
@fedepell
Copy link
Contributor

Thanks a lot for the immediate action! I can confirm also on my side that applying your patch solves the issue!

I will report to the Fedora Python team about the Union change as I think they have more contacts with Python team.

As we get newer preview versions in Fedora will keep rebuilding there and let you know if I spot anything else!

@pekkaklarck
Copy link
Member Author

Sounds good. Although the Union change broke some of our unit tests, it most likely wouldn't have caused any issues with Robot in practice. It ought to only affect cases where Union is used as-is, not parameterized usages like Union[int, str], and something like arg: Union is not valid anyway.

pekkaklarck added a commit that referenced this issue Mar 21, 2025
See PEP 649 for details. Part of Python 3.14 support (#5352).
@pekkaklarck pekkaklarck modified the milestones: v7.3, v7.4 Apr 4, 2025
@pekkaklarck
Copy link
Member Author

I moved this issue from RF 7.3 scope to RF 7.4. The reason is that we are going to released the former in April, and I don't think we can declare official Python 3.14 support until it is in beta and all features are in. RF 7.3, and also earlier versions, is compatible with the current Python 3.14 alphas, though.

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