Skip to content

Update default values for ast.ImportFrom initialization #14308

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
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

hunterhogan
Copy link
Contributor

(.313) C:\clones\typeshed>py
Python 3.13.5 (tags/v3.13.5:6cb20a2, Jun 11 2025, 16:15:46) [MSC v.1943 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> yum = ast.ImportFrom('popeyes', [ast.alias('chicken')])
>>> ast.unparse(yum)
'from popeyes import chicken'
>>> print(yum.level)
None

This comment has been minimized.

Co-authored-by: Semyon Moroz <donbarbos@proton.me>

This comment has been minimized.

@hunterhogan
Copy link
Contributor Author

Reverse chronological

3.15

(.venv) C:\clones\cpython>pcbuild\amd64\python_d
Python 3.15.0a0 (heads/main:28c71ee4b2e, Jun 17 2025, 22:00:57) [MSC v.1944 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> ast.ImportFrom()
ImportFrom(module=None, names=[], level=None)
>>> 

3.14, not installed or compiled

3.13 (also, see above)

Default values

(.313) C:\clones\typeshed>py
Python 3.13.5 (tags/v3.13.5:6cb20a2, Jun 11 2025, 16:15:46) [MSC v.1943 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> turkey = ast.ImportFrom()
>>> print(f"{turkey.module = }, {turkey.names = }, {turkey.level = }")
turkey.module = None, turkey.names = [], turkey.level = None
>>> ast.unparse(turkey)
'from  import '
>>> 

overload still necessary

(.313) C:\clones\typeshed>py
Python 3.13.5 (tags/v3.13.5:6cb20a2, Jun 11 2025, 16:15:46) [MSC v.1943 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> deepEnd = ast.ImportFrom([ast.alias('thePool')])                                                                                                                   
>>> print(f"{deepEnd.module = }, {deepEnd.names = }, {deepEnd.level = }")
deepEnd.module = [<ast.alias object at 0x000001E3BDF2F310>], deepEnd.names = [], deepEnd.level = None
>>> deepEnd = ast.ImportFrom(None, [ast.alias('thePool')])
>>> print(f"{deepEnd.module = }, {deepEnd.names = }, {deepEnd.level = }")
deepEnd.module = None, deepEnd.names = [<ast.alias object at 0x000001E3BDF60090>], deepEnd.level = None

3.12

keyword usage

>>> import ast
>>> silky = ast.ImportFrom()
>>> print(f"{silky.module = }, {silky.names = }, {silky.level = }")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ImportFrom' object has no attribute 'names'
>>> silky = ast.ImportFrom(names=[ast.alias('punto', 'period')])
>>> print(f"{silky.module = }, {silky.names = }, {silky.level = }")
silky.module = None, silky.names = [<ast.alias object at 0x000001EB300D9050>], silky.level = None
>>> ast.unparse(silky)
'from  import punto as period'
>>>

Note that I copy-pasted 'from import punto as period' and there are two spaces without a . between "from" and "import". Not related to typing, but maybe it's an issue.

no keyword, no happy

>>> tired = ast.ImportFrom([ast.alias('stomachache')])
>>> print(f"{tired.module = }, {tired.names = }, {tired.level = }")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ImportFrom' object has no attribute 'names'
>>> tired.module
[<ast.alias object at 0x000001EB3023AF90>]
>>> print(f"{tired.level = }")                                     
tired.level = None
>>> tired = ast.ImportFrom(None, [ast.alias('stomachache')])
>>> print(f"{tired.module = }, {tired.names = }, {tired.level = }")
tired.module = None, tired.names = [<ast.alias object at 0x000001EB3023AA10>], tired.level = None

3.11

(.311) C:\clones\typeshed>py
Python 3.11.9 (tags/v3.11.9:de54cf5, Apr  2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> blue = ast.ImportFrom(names=[ast.alias('moon')])
>>> print(f"{blue.module = }, {blue.names = }, {blue.level = }")
blue.module = None, blue.names = [<ast.alias object at 0x00000156222876D0>], blue.level = None
>>> ast.unparse(blue)
'from  import moon'
>>>

Also two spaces, no dot.

3.10

(.310) C:\clones\typeshed>py
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> quizas = ast.ImportFrom(names=[ast.alias('manana')])
>>> print(f"{quizas.module = }, {quizas.names = }, {quizas.level = }")
quizas.module = None, quizas.names = [<ast.alias object at 0x00000194028DE050>], quizas.level = None
>>> ast.unparse(quizas)
'from  import manana'
>>>

Ditto, no dotto.

3.9

(.309) C:\clones\typeshed>py
Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> play = ast.ImportFrom(names=[ast.alias('Mad_Libs')])
>>> print(f"{play.module = }, {play.names = }, {play.level = }")
play.module = None, play.names = [<ast.alias object at 0x000002590DBB7FD0>], play.level = None
>>> ast.unparse(play)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\hunte\AppData\Local\Programs\Python\Python39\lib\ast.py", line 1572, in unparse
    return unparser.visit(ast_obj)
  File "C:\Users\hunte\AppData\Local\Programs\Python\Python39\lib\ast.py", line 801, in visit
    self.traverse(node)
  File "C:\Users\hunte\AppData\Local\Programs\Python\Python39\lib\ast.py", line 795, in traverse
    super().visit(node)
  File "C:\Users\hunte\AppData\Local\Programs\Python\Python39\lib\ast.py", line 407, in visit
    return visitor(node)
  File "C:\Users\hunte\AppData\Local\Programs\Python\Python39\lib\ast.py", line 846, in visit_ImportFrom
    self.write("." * node.level)
TypeError: can't multiply sequence by non-int of type 'NoneType'
>>> import ast
>>> Europe = ast.ImportFrom(names=[ast.alias('zero', 'Arabic_numerals')], level = 0)
>>> print(f"{Europe.module = }, {Europe.names = }, {Europe.level = }")
Europe.module = None, Europe.names = [<ast.alias object at 0x000002590D88E4C0>], Europe.level = 0
>>> ast.unparse(Europe)
'from  import zero as Arabic_numerals'
>>>

So, level is required and cannot be None?

And the dot is still missing: from[space][space]import...

New commit, failing tests

I'll submit a new commit. I see failing tests above, but I haven't looked at them.

…rsion and enhance constructor overloads for better compatibility

This comment has been minimized.

@hunterhogan
Copy link
Contributor Author

Local test failures

On my local system, I ran

(.venv) C:\clones\typeshed>ver & py -V

Microsoft Windows [Version 10.0.26100.4351]
Python 3.12.10

(.venv) C:\clones\typeshed>tests\runtests.py stdlib/ast.pyi

...

Running stubtest...
c:\clones\typeshed\.venv\Scripts\python.exe -m mypy.stubtest --check-typeshed --show-traceback --custom-typeshed-dir . --allowlist stdlib\@tests\stubtest_allowlists\common.txt --allowlist stdlib\@tests\stubtest_allowlists\win32.txt --allowlist stdlib\@tests\stubtest_allowlists\py312.txt --allowlist stdlib\@tests\stubtest_allowlists\win32-py312.txt
note: unused allowlist entry _ast.ImportFrom.level
note: unused allowlist entry ast.ImportFrom.level
Found 2 errors (checked 732 modules)

That lead me to "stdlib@tests\stubtest_allowlists\common.txt" and the entries

_ast.ImportFrom.level  # None on the class, but never None on instances

argparse.Namespace.__setattr__  # should allow setting any attribute

ast.ImportFrom.level  # None on the class, but never None on instances

This aspect of the testing process was unknown to me before now. I don't know if or how I should address it.

GitHub actions test failures

I looked at the Window Py 3.13 test, and the GitHub test failures are the same as my local failure. ("... my local failure." That was a little too on-the-nose.)

Failure is good; war is peace.

Claude 4 in VS Code Copilot assured me that the test failures were a good thing.

The failing tests in Prefect are actually validating that your type improvements are working correctly. You might want to add a comment explaining that the mypy_primer failures are expected and beneficial - they're exposing existing type safety issues in downstream code.

Therefore, you're welcome.

I don't know what I should do next, if anything.

This comment has been minimized.

Copy link
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

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

Successfully merging this pull request may close these issues.

2 participants