blob: 80100e69a64212fb3388723cfecd6cac3ae1ee6b [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Post-process the stubs from ``maturin --generate-stubs``.
``maturin`` writes them under ``opendal/_opendal/``, but the public API and the
classes' ``module=`` use ``opendal.<name>``; type checkers resolve from the
filesystem, so the stubs must live at ``opendal/<name>.pyi``. This relocates
them and applies two temporary fixups (until PyO3 introspection covers them):
1. Inject the imports our forward-ref string annotations need (PyO3 writes the
annotation but not the import).
2. Replace ``exceptions.pyi`` with a hand-written stub while the generated one is
incomplete -- ``create_exception!`` types are not introspectable yet, so PyO3
emits only ``__getattr__``. When that changes, the stub is kept as-is.
"""
from __future__ import annotations
from pathlib import Path
PKG = Path(__file__).resolve().parent.parent / "python" / "opendal"
GEN = PKG / "_opendal"
# Public ``opendal.<name>`` submodules whose stubs are relocated to ``<name>.pyi``.
SUBMODULES = (
"capability",
"exceptions",
"file",
"layers",
"operator",
"services",
"types",
)
# Imports for forward-ref string annotations that PyO3 does not emit. Relative
# imports stay valid after relocation (the siblings move together).
IMPORTS = {
"operator": (
"import collections.abc\n"
"from .file import AsyncFile\n"
"from .services import Scheme\n"
"from .types import Entry, PresignedRequest\n"
),
"file": "import collections.abc\nimport types\nimport typing_extensions\n",
}
# Mirrors the `create_exception!` types in `src/errors.rs`; keep in sync.
EXCEPTIONS_STUB = """\
class Error(Exception): ...
class Unexpected(Exception): ...
class Unsupported(Exception): ...
class ConfigInvalid(Exception): ...
class NotFound(Exception): ...
class PermissionDenied(Exception): ...
class IsADirectory(Exception): ...
class NotADirectory(Exception): ...
class AlreadyExists(Exception): ...
class IsSameFile(Exception): ...
class ConditionNotMatch(Exception): ...
class RateLimited(Exception): ...
class RangeNotSatisfied(Exception): ...
"""
def fix_incomplete_exceptions() -> None:
"""Use the hand-written exceptions stub while the generated one is incomplete."""
path = GEN / "exceptions.pyi"
if "__getattr__" in path.read_text():
path.write_text(EXCEPTIONS_STUB)
def relocate() -> None:
"""Move the stubs to the public ``opendal/<name>.pyi`` paths."""
for name in SUBMODULES:
text = (GEN / f"{name}.pyi").read_text()
(PKG / f"{name}.pyi").write_text(IMPORTS.get(name, "") + text)
# Stub for the ``opendal/_opendal.*.so`` extension itself.
(PKG / "_opendal.pyi").write_text((GEN / "__init__.pyi").read_text())
for child in GEN.iterdir():
child.unlink()
GEN.rmdir()
def main() -> None:
"""Apply the fixups, then relocate to the public paths."""
fix_incomplete_exceptions()
relocate()
if __name__ == "__main__":
main()