Skip to content

Added type hints to _asdf.py and config.py#2031

Merged
sydduckworth merged 17 commits into
asdf-format:mainfrom
sydduckworth:type-checking-fixes
May 1, 2026
Merged

Added type hints to _asdf.py and config.py#2031
sydduckworth merged 17 commits into
asdf-format:mainfrom
sydduckworth:type-checking-fixes

Conversation

@sydduckworth
Copy link
Copy Markdown
Contributor

@sydduckworth sydduckworth commented Apr 24, 2026

Changes

  • Added type hints to all methods and functions in _asdf.py and config.py. Added type hints to other modules as needed to support those two files.
  • No user-facing changes other than the addition of typing (and I guess the change to NotSet described below)
  • A small number of code refactors that shouldn't change functionality:
    • Defragment command now checks if the output file is None before writing to it
    • In get_file added an isinstance check to the special case where the input is stdin/stdout/stderr so that type narrowing works correctly
    • In get_file updated RandomAccessFile and subclasses so that the reference to their original file handle is passed in the constructor instead of being set separately
    • Removed some import logic in jsonschema which only existed to support Python versions < 3.9
  • Reworked the NotSet constant in a way that plays nicely with the Python type system
    • The underlying class has been reworked to use the same sentinel value pattern as dataclasses.MISSING
    • The NotSet constant has been renamed to NOT_SET because apparently a global being in all-caps makes type-checkers/linters assume it's immutable
    • Added NotSet as a type alias for the typing needed to use the NOT_SET constant.
    • Usage in function signatures is myparam: str | NotSet = NOT_SET. Usage in function bodies is the same and now doing value is not NOT_SET correctly applies type narrowing in the type-checker.
  • Added typing.py module which contains new type aliases and protocols
  • Added some type-hints to tests where type inference failed but otherwise tried to avoid annotating tests. For tests specifically testing incorrect arguments added # pyrefly: ignore

Potential Changes/Future Work

TreeKey

I added a TreeKey type alias in asdf.typing to be used for typing dictionaries that can be converted into ASDF trees.
The original intention was that it would be str | int | bool or similar, but it turns out that

  1. For various reasons Python decided that although map values are covariant, map keys are invariant
  2. The specific types in a union are considered subtypes of the union.

The end result is that you wouldn't be able to assign a dict[str, Any] to a dict[TreeKey, Any] field without manually widening the str type first.

So for now I've just set TreeKey = Any, with the idea that if we find a way in the future to narrow the type without breaking everything we can update TreeKey and not have to update every individual place its used.

Compression

I added a type alias ArrayStorage which aliases to the different string literal values that are valid for array_storage. This has the nice effect that the Python LSP will auto-suggest the valid options when you're typing the value.

I added another type alias Compression which was intended to do the same thing for the supported options for array_compression.
The complication is that array compression can be one of the predefined strings or it can also be any arbitrary string supported by an extension.
So I set Compression = Literal["lz4", ...] | str | None, which works with the type-checker, but it looks like adding str to the union means the LSP won't auto-suggest the valid options.
I think the Literal | str union is still worth keeping rather than just str since it provides more information to the user and maybe in the future we can find a way to get it to work with the LSP.

Documentation

  • I added asdf.typing to the User API section, but it might make more sense in the developer API section. Then again, with the addition of type hints we might want to rethink how we organize the API docs in the future anyway.
  • In a lot of places the type-hints contradict the types in the associated function docstring. We should consider removing types from the docstrings of functions that have type hints.
  • There are a lot of issues I ran into getting Sphinx to work with type hints that I will put in a separate issue.

Sorry about the size of this PR 😅

@sydduckworth sydduckworth marked this pull request as ready for review April 29, 2026 14:47
@sydduckworth sydduckworth requested a review from a team as a code owner April 29, 2026 14:47
@sydduckworth sydduckworth requested a review from braingram April 29, 2026 14:47
Comment thread asdf/_commands/defragment.py
Comment thread asdf/_tests/test_array_blocks.py
Comment thread asdf/_asdf.py
Comment thread asdf/_io.py
Comment thread asdf/generic_io.py
Comment thread asdf/typing.py Outdated
Comment thread asdf/typing.py
Comment thread changes/2031.feature.rst
Comment thread asdf/typing.py Outdated
@zacharyburnett
Copy link
Copy Markdown
Member

this looks pretty comprehensive to me, I didn't know about PEP647 and type narrowing until just now 😅

Copy link
Copy Markdown
Member

@zacharyburnett zacharyburnett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM especially as there are no functional differences

Copy link
Copy Markdown
Contributor

@braingram braingram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together and the detailed write-up. I left one comment about NOT_SET but it's not actionable at the moment. I didn't scrutinize all the hints and don't think that's necessary at this point (and with zach's review, thanks!).

Comment thread asdf/util.py

#: Special value indicating that a parameter is not set.
#: Distinct from None, which may for example be a value of interest in a search.
NOT_SET: Final = _NOT_SET_TYPE.NOT_SET
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can yearn for the day we have 3.15 as a min: https://peps.python.org/pep-0661/

@sydduckworth sydduckworth merged commit 22fa187 into asdf-format:main May 1, 2026
45 checks passed
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.

3 participants