Skip to content

Deprecate top-level re-exports #1090

Open

Description

@provinzkraut

While investigating #884, I discovered a bit of a roadblock.

currently, in msgspec/__init__.py we have the following:

from . import inspect, json, msgpack, structs, toml, yaml

which means that all submodules will always be imported. Especially inspect is a heavy hitter, since it imports typing, typing_extensions and a whole bunch of other stuff. This means that, even if we make the core extension super lightweight on imports, we won't see a noticeable difference, unless we remove those imports.

Functionally, the difference is that currently you can do: import msgspec; msgspec.inspect.is_struct(), whereas with these imports removed, you'd have to do import msgspec.inspect; msgspec.inspect.is_struct().

I think it's worth doing, and it's standard practice, however, it would be a breaking change. If we do this, it'd be a 2 part change: 1. Mark them deprecated, 2. Remove them. Since we're currently in 0ver, we could do this in a minor release, but we haven't had much breaking changes, so we should give ample time to users to react.

@msgspec/committers, I'd like to know your opinion on this.

Activity

  1. provinzkraut commented on Jun 19, 2026

    @provinzkraut
    MemberAuthor

    As for the performance impact: Judging by some crude testing, I think we can easily bring down the import time by ~50%, with a bit more effort maybe ~75%.

  2. ofek commented on Jun 19, 2026

    @ofek
    Member

    Can we start the first phase immediately and use PEP 562? We would gain the immediate benefit of their "removal" while simultaneously emitting deprecation warnings.

  3. sobolevn commented on Jun 21, 2026

    @sobolevn
    Member

    In my opinion deprecation is not really worth it.

    Reasons:

    1. Python 3.15 would have lazy import statement
    2. Current msgspec API seems rather compact and simple
    3. We can use something like (pseudocode) to achieve the same thing:
    def __getattr__(name):
        if name == 'inspect':
            from . import inspect
            return inspect
    1. It is good to have a stable user API :)
  4. ofek commented on Jun 21, 2026

    @ofek
    Member

    In my previous comment I proposed basically 3. with deprecation warnings. It's fine to keep them for a while but we shouldn't maintain an API that we don't want indefinitely.

    You bring up an interesting point however: is our code compatible with the new lazy imports? I can't recall if there was a PR that added support.

  5. sobolevn commented on Jun 21, 2026

    @sobolevn
    Member

    I will check this out and report back :)

  6. sobolevn commented on Jun 21, 2026

    @sobolevn
    Member

    pytest does not work with this new PYTHON_LAZY_IMPORTS=all mode: pytest-dev/pytest#14632 So, I can't tell if the tests pass 😆

  7. RonnyPfannschmidt commented on Jun 21, 2026

    @RonnyPfannschmidt

    Try with assertion rewrite disabled

  8. Siyet commented on Jul 3, 2026

    @Siyet
    Contributor

    My 2c: lazy __getattr__ without a deprecation period, i.e. what @sobolevn suggested.

    • The import-time win lands immediately and identically either way. A deprecation only buys us the option to eventually delete the __getattr__ hook itself, which is ~15 lines we can keep forever at zero cost.
    • The churn side of a DeprecationWarning is real and ecosystem-wide: import msgspec; msgspec.json.encode(...) is the spelling in half the tutorials and downstream libraries. Everyone gets warning noise, everyone ships a trivial fix release, and what we get back is deleting a tiny hook.
    • I'd still update the docs to prefer import msgspec.json as the canonical spelling going forward, so new code trends the right way without forcing old code to move.

    One thing to settle before landing: the interplay with 3.15 lazy imports (#1095) - our own __getattr__ laziness and PYTHON_LAZY_IMPORTS should compose, and sobolevn is already digging there.

  9. ofek commented on Jul 3, 2026

    @ofek
    Member

    ~15 lines we can keep forever at zero cost

    We will not be doing this.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions