Compare commits
3 Commits
70c7e334a7
...
66ac7863b5
| Author | SHA1 | Date |
|---|---|---|
|
|
66ac7863b5 | |
|
|
089e158da9 | |
|
|
7987f6b8d7 |
|
|
@ -0,0 +1,92 @@
|
|||
'''
|
||||
Regression tests for the cold package import surface.
|
||||
|
||||
'''
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import (
|
||||
Any,
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
from tractor.discovery import (
|
||||
_addr,
|
||||
_multiaddr,
|
||||
)
|
||||
from tractor.ipc import (
|
||||
_tcp,
|
||||
_uds,
|
||||
)
|
||||
|
||||
|
||||
def run_cold_import(code: str) -> dict[str, object]:
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
'-c',
|
||||
code,
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def test_lazy_to_asyncio_package_api():
|
||||
'''
|
||||
Keep the public lazy submodule discoverable without eagerly
|
||||
importing it.
|
||||
|
||||
Before the lazy conversion, package import side effects exposed
|
||||
`to_asyncio` to `dir()` and wildcard imports. Exercise those APIs
|
||||
in cold interpreters so this test proves normal `import tractor`
|
||||
leaves `asyncio` unloaded, while discovery and wildcard access
|
||||
still advertise and resolve the public submodule.
|
||||
|
||||
'''
|
||||
cold = run_cold_import(
|
||||
'import json, sys, tractor; '
|
||||
'print(json.dumps({'
|
||||
'"advertised": "to_asyncio" in dir(tractor), '
|
||||
'"asyncio_loaded": "asyncio" in sys.modules}))'
|
||||
)
|
||||
assert cold == {
|
||||
'advertised': True,
|
||||
'asyncio_loaded': False,
|
||||
}
|
||||
|
||||
wildcard = run_cold_import(
|
||||
'import json; '
|
||||
'from tractor import *; '
|
||||
'print(json.dumps({'
|
||||
'"module": to_asyncio.__name__}))'
|
||||
)
|
||||
assert wildcard == {
|
||||
'module': 'tractor.to_asyncio',
|
||||
}
|
||||
|
||||
|
||||
def test_lazy_annotation_names_resolve():
|
||||
'''
|
||||
Resolve annotations without importing optional dependencies.
|
||||
|
||||
Moving annotation-only third-party names under `TYPE_CHECKING`
|
||||
left their runtime globals undefined, causing
|
||||
`typing.get_type_hints()` to raise `NameError`. Resolve every
|
||||
affected API and prove the lazy aliases retain import-free runtime
|
||||
introspection.
|
||||
|
||||
'''
|
||||
assert get_type_hints(_multiaddr.mk_maddr)['return'] is Any
|
||||
assert get_type_hints(_tcp.MsgpackTCPStream.maddr.fget)[
|
||||
'return'
|
||||
] is Any
|
||||
assert get_type_hints(_uds.MsgpackUDSStream.maddr.fget)[
|
||||
'return'
|
||||
] == Any|str
|
||||
assert get_type_hints(_addr.Address.get_random)[
|
||||
'current_actor'
|
||||
] is Any
|
||||
assert _addr.__annotations__['_address_types'].startswith('dict')
|
||||
|
|
@ -2,9 +2,11 @@
|
|||
`tractor.log`-wrapping unit tests.
|
||||
|
||||
'''
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
|
|
@ -165,6 +167,53 @@ def test_implicit_mod_name_applied_for_child(
|
|||
assert submod.log.logger in sub_logs
|
||||
|
||||
|
||||
def test_implicit_mod_name_from_unregistered_namespace(
|
||||
tmp_path: Path,
|
||||
):
|
||||
'''
|
||||
Preserve implicit logger naming for dynamic module namespaces.
|
||||
|
||||
The fast `sys.modules` caller lookup cannot resolve `runpy`,
|
||||
plugin-loader, or `exec()` namespaces that are not registered.
|
||||
Compile a real package file under an unregistered module name so
|
||||
the rare filename fallback must recover its imported package and
|
||||
retain the same package-level logger name.
|
||||
|
||||
'''
|
||||
pkg_name = 'dynamic_logger_pkg'
|
||||
pkg_dir = tmp_path / pkg_name
|
||||
pkg_dir.mkdir()
|
||||
init_path = pkg_dir / '__init__.py'
|
||||
init_path.write_text('')
|
||||
mod_path = pkg_dir / 'plugin.py'
|
||||
mod_path.write_text('')
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
importlib.import_module(pkg_name)
|
||||
namespace = {
|
||||
'__name__': f'{pkg_name}.unregistered',
|
||||
'__package__': pkg_name,
|
||||
'tractor': tractor,
|
||||
}
|
||||
exec(
|
||||
compile(
|
||||
'log = tractor.log.get_logger('
|
||||
f'pkg_name={pkg_name!r})',
|
||||
str(mod_path),
|
||||
'exec',
|
||||
),
|
||||
namespace,
|
||||
)
|
||||
dynamic_log = namespace.get('log')
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
sys.modules.pop(pkg_name, None)
|
||||
|
||||
assert dynamic_log is not None
|
||||
assert dynamic_log.name == pkg_name
|
||||
|
||||
|
||||
def test_io_custom_level_registered():
|
||||
'''
|
||||
The `IO`(21) level (registered via `add_log_level()` at
|
||||
|
|
|
|||
|
|
@ -76,6 +76,19 @@ from .discovery._registry import (
|
|||
# from . import hilevel as hilevel
|
||||
|
||||
|
||||
__all__: tuple[str, ...] = tuple(
|
||||
name
|
||||
for name in globals()
|
||||
if not name.startswith('_')
|
||||
) + (
|
||||
'to_asyncio',
|
||||
)
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return sorted(set(globals()) | set(__all__))
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
'''
|
||||
PEP 562 lazy sub-module loading, presently only for
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
from __future__ import annotations
|
||||
from uuid import uuid4
|
||||
from typing import (
|
||||
Any,
|
||||
Protocol,
|
||||
ClassVar,
|
||||
Type,
|
||||
|
|
@ -36,8 +37,9 @@ from ..ipc._uds import UDSAddress
|
|||
if TYPE_CHECKING:
|
||||
# ONLY type-annots, the eager import costs ~4.5ms
|
||||
# of `import tractor` wall-time (gh #470).
|
||||
from bidict import bidict
|
||||
from ..runtime._runtime import Actor
|
||||
else:
|
||||
Actor = Any
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
|
@ -172,7 +174,7 @@ class Address(Protocol):
|
|||
...
|
||||
|
||||
|
||||
_address_types: bidict[str, Type[Address]] = {
|
||||
_address_types: dict[str, Type[Address]] = {
|
||||
'tcp': TCPAddress,
|
||||
'uds': UDSAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ Multiaddress support using the upstream `py-multiaddr` lib
|
|||
from __future__ import annotations
|
||||
import ipaddress
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import (
|
||||
Any,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# NOTE, `multiaddr` is lazy-imported at first use
|
||||
|
|
@ -35,6 +38,9 @@ if TYPE_CHECKING:
|
|||
# `import tractor` path (gh #470).
|
||||
from multiaddr import Multiaddr
|
||||
from tractor.discovery._addr import Address
|
||||
else:
|
||||
Multiaddr = Any
|
||||
Address = Any
|
||||
|
||||
# map from tractor-internal `proto_key` identifiers
|
||||
# to the standard multiaddr protocol name strings.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ TCP implementation of tractor.ipc._transport.MsgTransport protocol
|
|||
from __future__ import annotations
|
||||
import ipaddress
|
||||
from typing import (
|
||||
Any,
|
||||
ClassVar,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
|
@ -46,6 +47,8 @@ if TYPE_CHECKING:
|
|||
# ONLY type-annots, the eager import costs
|
||||
# `import tractor` wall-time (gh #470).
|
||||
from multiaddr import Multiaddr
|
||||
else:
|
||||
Multiaddr = Any
|
||||
|
||||
|
||||
log = get_logger()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from socket import (
|
|||
)
|
||||
import struct
|
||||
from typing import (
|
||||
Any,
|
||||
Type,
|
||||
TYPE_CHECKING,
|
||||
ClassVar,
|
||||
|
|
@ -66,6 +67,9 @@ if TYPE_CHECKING:
|
|||
# `import tractor` wall-time (gh #470).
|
||||
from multiaddr import Multiaddr
|
||||
from tractor.runtime._runtime import Actor
|
||||
else:
|
||||
Multiaddr = Any
|
||||
Actor = Any
|
||||
|
||||
|
||||
# Platform-specific credential passing constants
|
||||
|
|
|
|||
|
|
@ -463,7 +463,14 @@ def get_logger(
|
|||
if mod_name is None:
|
||||
return None
|
||||
|
||||
return sys.modules.get(mod_name)
|
||||
if caller_mod := sys.modules.get(mod_name):
|
||||
return caller_mod
|
||||
|
||||
# Preserve caller discovery for `runpy`, plugin loaders,
|
||||
# and `exec()` namespaces not registered in `sys.modules`.
|
||||
# Import `inspect` only on this rare fallback path.
|
||||
from inspect import getmodule
|
||||
return getmodule(caller_frame)
|
||||
|
||||
# --- Auto--naming-CASE ---
|
||||
# -------------------------
|
||||
|
|
|
|||
Loading…
Reference in New Issue