tessl install tessl/pypi-typeshed-client@2.8.4A library for accessing stubs in typeshed.
The resolver module provides functionality for resolving fully qualified names to their definitions, following imports and re-exports across module boundaries. This is useful for finding where a name is actually defined, even when it's imported and re-exported by multiple modules.
| Method/Function | Return Type | Can Return None? |
|---|---|---|
Resolver.__init__(search_context) | Resolver | No |
resolver.get_fully_qualified_name(name) | ResolvedName | Yes (None means not found) |
resolver.get_name(module_name, name) | ResolvedName | Yes (None means not found) |
resolver.get_module(module_name) | Module | No (check Module.exists) |
module.get_name(name, resolver) | ResolvedName | Yes |
module.get_dunder_all(resolver) | Optional[list[str]] | Yes |
ResolvedName Type: Union[ModulePath, ImportedInfo, NameInfo, None]
The main class for resolving names across modules.
class Resolver:
"""
Resolves fully qualified names to their definitions.
Maintains caches for modules and names to avoid redundant parsing.
Thread Safety: Not thread-safe; do not share instances across threads.
"""
def __init__(self, search_context: Optional[SearchContext] = None) -> None:
"""
Create a new Resolver.
Args:
search_context (SearchContext, optional): Context for finding stubs. Uses default if None.
Example:
resolver = Resolver()
# Or with custom context:
ctx = get_search_context(version=(3, 11))
resolver = Resolver(search_context=ctx)
"""Key Points:
Resolves a dotted name string to its definition.
def get_fully_qualified_name(self, name: str) -> ResolvedName:
"""
Resolve a fully qualified name to its definition.
Args:
name (str): Fully qualified name as dotted string (e.g., 'collections.OrderedDict')
Returns:
ResolvedName: One of:
- ModulePath: If the name refers to a module
- ImportedInfo: If the name is imported, with source module and NameInfo
- NameInfo: If the name is defined directly in the module
- None: If the name cannot be resolved
Example:
resolver = Resolver()
# Resolve collections.OrderedDict
info = resolver.get_fully_qualified_name('collections.OrderedDict')
# Resolve typing.List
info = resolver.get_fully_qualified_name('typing.List')
"""Return Value Details:
None: Name not found (module doesn't exist or name not in module)ModulePath: Name refers to a module (e.g., 'collections' resolves to module)ImportedInfo: Name is imported; contains source_module and info fieldsNameInfo: Name is defined directly in the moduleCaching: Results are cached; subsequent calls with same name return cached result
Resolves a name within a specific module.
def get_name(self, module_name: ModulePath, name: str) -> ResolvedName:
"""
Get a name from a specific module.
Args:
module_name (ModulePath): Module path as tuple of strings
name (str): Name to look up
Returns:
ResolvedName: Resolved name information
Example:
from typeshed_client import Resolver, ModulePath
resolver = Resolver()
info = resolver.get_name(ModulePath(('collections',)), 'OrderedDict')
"""Return Value Details:
get_fully_qualified_name()None if module doesn't exist or name not foundUsage Note: Use ModulePath(('module', 'submodule')) to create ModulePath
Retrieves a Module object for a given module path.
def get_module(self, module_name: ModulePath) -> Module:
"""
Get a Module object for the given module name.
Results are cached to avoid redundant parsing.
Args:
module_name (ModulePath): Module path as tuple of strings
Returns:
Module: Module object with name lookup capabilities
Example:
from typeshed_client import Resolver, ModulePath
resolver = Resolver()
module = resolver.get_module(ModulePath(('typing',)))
print(module.exists) # True
print(len(module.names)) # Number of names in typing module
"""Return Value Details:
Module object (never None)Module.exists to see if module was actually foundmodule.exists == False and module.names == {}Performance: First call parses module; subsequent calls use cache
Represents a parsed module with name lookup capabilities. Module objects are obtained via Resolver.get_module() and are not typically instantiated directly by users.
class Module:
"""
A parsed module with name lookup.
Module objects are created by Resolver.get_module() and should not be
instantiated directly by users.
Attributes:
names (NameDict): Dictionary of names defined in the module
ctx (SearchContext): Search context used for parsing
exists (bool): Whether the module exists
"""
names: NameDict
ctx: SearchContext
exists: bool
def __init__(
self,
names: NameDict,
ctx: SearchContext,
*,
exists: bool = True
) -> None:
"""
Initialize a Module object.
Note: Module objects are typically created by Resolver.get_module()
and should not be instantiated directly by users.
Args:
names (NameDict): Dictionary of names defined in the module
ctx (SearchContext): Search context used for parsing
exists (bool, optional): Whether the module exists. Default: True
"""
def get_name(self, name: str, resolver: Resolver) -> ResolvedName:
"""
Look up a name in this module.
Results are cached to avoid redundant lookups.
Args:
name (str): Name to look up
resolver (Resolver): Resolver instance for following imports
Returns:
ResolvedName: Resolved name information
Example:
resolver = Resolver()
module = resolver.get_module(ModulePath(('typing',)))
info = module.get_name('List', resolver)
"""
def get_dunder_all(self, resolver: Resolver) -> Optional[list[str]]:
"""
Return the contents of __all__, or None if it does not exist.
Args:
resolver (Resolver): Resolver instance for following imports
Returns:
list[str]: List of names in __all__, or None if __all__ is not defined
Example:
from typeshed_client import Resolver, ModulePath
resolver = Resolver()
module = resolver.get_module(ModulePath(('typing',)))
all_names = module.get_dunder_all(resolver)
print(all_names) # ['Any', 'Union', 'Optional', ...]
"""Key Points:
exists attribute indicates if module was foundnames is empty dict if module doesn't existget_name() method caches results internallyget_dunder_all() returns None if __all__ not defined or cannot be extractedThe result of resolving a name can be one of four types:
ResolvedName = Union[ModulePath, ImportedInfo, NameInfo, None]Type Descriptions:
None: Name not found
ModulePath: Name refers to a module
'collections' resolves to ModulePath(('collections',))resolver.get_module() to access moduleImportedInfo: Name is imported from another module
source_module: ModulePath (where it's defined)info: NameInfo (information about the name)NameInfo: Name is defined directly in the module
Usage Pattern:
result = resolver.get_fully_qualified_name('some.name')
if result is None:
print("Not found")
elif isinstance(result, ModulePath):
print(f"Module: {'.'.join(result)}")
elif isinstance(result, ImportedInfo):
print(f"Imported from: {'.'.join(result.source_module)}")
print(f"Name: {result.info.name}")
elif isinstance(result, NameInfo):
print(f"Defined directly: {result.name}")When a name is imported from another module, it's wrapped in an ImportedInfo:
class ImportedInfo(NamedTuple):
"""
Information about an imported name with its source module.
Attributes:
source_module (ModulePath): Module where the name originates
info (NameInfo): Information about the name
"""
source_module: ModulePath
info: NameInfoUsage:
from typeshed_client import Resolver, ImportedInfo
resolver = Resolver()
result = resolver.get_fully_qualified_name('collections.OrderedDict')
if isinstance(result, ImportedInfo):
print(f"Originally defined in: {'.'.join(result.source_module)}")
print(f"Name: {result.info.name}")
print(f"Exported: {result.info.is_exported}")
print(f"AST type: {type(result.info.ast).__name__}")from typeshed_client import Resolver
resolver = Resolver()
# Resolve typing.List
result = resolver.get_fully_qualified_name('typing.List')
if result:
print(f"typing.List resolved to: {type(result).__name__}")
else:
print("typing.List not found")
# Resolve collections.OrderedDict
result = resolver.get_fully_qualified_name('collections.OrderedDict')
if result:
print(f"collections.OrderedDict resolved to: {type(result).__name__}")
else:
print("collections.OrderedDict not found")
# Try non-existent name
result = resolver.get_fully_qualified_name('nonexistent.Name')
if result is None:
print("nonexistent.Name not found")from typeshed_client import Resolver, ImportedInfo, NameInfo, ModulePath
resolver = Resolver()
# Some names may be re-exported from other modules
result = resolver.get_fully_qualified_name('collections.OrderedDict')
if result is None:
print("Not found")
elif isinstance(result, ModulePath):
print(f"It's a module: {'.'.join(result)}")
elif isinstance(result, ImportedInfo):
print(f"Imported from: {'.'.join(result.source_module)}")
print(f"Original name: {result.info.name}")
print(f"Exported: {result.info.is_exported}")
# Access the actual NameInfo
info = result.info
print(f"AST type: {type(info.ast).__name__}")
elif isinstance(result, NameInfo):
print(f"Defined directly in collections")
print(f"Name: {result.name}")from typeshed_client import Resolver, ModulePath
resolver = Resolver()
names_to_check = ['collections', 'collections.OrderedDict', 'typing.List']
for name in names_to_check:
result = resolver.get_fully_qualified_name(name)
if isinstance(result, ModulePath):
print(f"✓ '{name}' is a module")
elif result is not None:
print(f"✗ '{name}' is not a module, it's a {type(result).__name__}")
else:
print(f"✗ '{name}' not found")from typeshed_client import Resolver, NameInfo, ImportedInfo
import ast
resolver = Resolver()
# Get the definition of typing.Union
result = resolver.get_fully_qualified_name('typing.Union')
# Extract NameInfo from result
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
if info:
# Access the AST node
if isinstance(info.ast, ast.Assign):
print(f"typing.Union is defined as an assignment")
elif isinstance(info.ast, ast.ClassDef):
print(f"typing.Union is a class")
elif isinstance(info.ast, ast.FunctionDef):
print(f"typing.Union is a function")
# Check if it's exported
print(f"Exported: {info.is_exported}")
else:
print("Could not extract NameInfo")from typeshed_client import Resolver, ModulePath
resolver = Resolver()
# Get the typing module
module = resolver.get_module(ModulePath(('typing',)))
# Check if module exists
if module.exists:
print(f"✓ typing module found")
print(f" Names: {len(module.names)}")
# Get __all__
all_names = module.get_dunder_all(resolver)
if all_names:
print(f" Exports {len(all_names)} names via __all__")
print(f" First 5: {all_names[:5]}")
else:
print(f" No __all__ defined")
# Look up a specific name
result = module.get_name('List', resolver)
if result:
print(f" typing.List: {type(result).__name__}")
else:
print(f" typing.List not found")
else:
print("✗ typing module not found")
# Try non-existent module
bad_module = resolver.get_module(ModulePath(('nonexistent',)))
print(f"\nnonexistent module exists: {bad_module.exists}")
print(f"nonexistent module names: {len(bad_module.names)}")from typeshed_client import Resolver, ModulePath
resolver = Resolver()
# Resolve 'deque' from collections module
result = resolver.get_name(ModulePath(('collections',)), 'deque')
if result:
print(f"collections.deque: {type(result).__name__}")
else:
print("collections.deque not found")
# Resolve 'OrderedDict' from collections module
result = resolver.get_name(ModulePath(('collections',)), 'OrderedDict')
if result:
print(f"collections.OrderedDict: {type(result).__name__}")
else:
print("collections.OrderedDict not found")
# Try non-existent name
result = resolver.get_name(ModulePath(('collections',)), 'NonExistent')
if result is None:
print("collections.NonExistent not found")from typeshed_client import Resolver, ImportedInfo, NameInfo, ModulePath, ImportedName
def find_original_definition(resolver: Resolver, qualified_name: str):
"""Follow imports to find the original definition."""
result = resolver.get_fully_qualified_name(qualified_name)
# Track the chain of imports
chain = [qualified_name]
# Follow ImportedInfo chain
while isinstance(result, ImportedInfo):
source_module = result.source_module
original_name = result.info.name
next_name = f"{'.'.join(source_module)}.{original_name}"
chain.append(next_name)
# Check if the info itself is an import
if isinstance(result.info.ast, ImportedName):
# Follow to the imported module
result = resolver.get_name(
result.info.ast.module_name,
result.info.ast.name if result.info.ast.name else original_name
)
else:
# Found the original definition
break
return result, chain
resolver = Resolver()
original, import_chain = find_original_definition(resolver, 'collections.OrderedDict')
print(f"Import chain: {' -> '.join(import_chain)}")
if original:
result_type = type(original).__name__
print(f"Original definition type: {result_type}")
# Extract NameInfo if available
if isinstance(original, NameInfo):
print(f"Name: {original.name}")
elif isinstance(original, ImportedInfo):
print(f"Name: {original.info.name}")
else:
print("Could not resolve to original definition")from typeshed_client import Resolver, NameInfo, ImportedInfo
import ast
resolver = Resolver()
# Get OrderedDict class
result = resolver.get_fully_qualified_name('collections.OrderedDict')
# Extract NameInfo
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
if info and isinstance(info.ast, ast.ClassDef):
class_def = info.ast
print(f"Class: {info.name}")
print(f"Exported: {info.is_exported}")
# Get base classes
if class_def.bases:
bases = [ast.unparse(base) for base in class_def.bases]
print(f"Base classes: {', '.join(bases)}")
# Get methods
if info.child_nodes:
public_methods = [
name for name, child_info in info.child_nodes.items()
if child_info.is_exported
]
private_methods = [
name for name, child_info in info.child_nodes.items()
if not child_info.is_exported
]
print(f"Public methods: {len(public_methods)}")
print(f"Private methods: {len(private_methods)}")
print(f"Some public methods: {public_methods[:5]}")
else:
print("No child nodes available")
else:
print("Not a class or not found")from typeshed_client import Resolver, get_search_context
versions = [(3, 9), (3, 11), (3, 12)]
name_to_check = 'typing.TypedDict'
print(f"Checking '{name_to_check}' across Python versions:")
for version in versions:
# Create version-specific context and resolver
ctx = get_search_context(version=version)
resolver = Resolver(search_context=ctx)
# Resolve the name
result = resolver.get_fully_qualified_name(name_to_check)
if result is None:
status = "Not found"
else:
status = f"Found ({type(result).__name__})"
print(f" Python {version[0]}.{version[1]}: {status}")from typeshed_client import Resolver, ModulePath
resolver = Resolver()
# Check if modules exist
modules_to_check = [
('typing',),
('collections',),
('collections', 'abc'),
('nonexistent_module',),
]
print("Module existence check:")
for module_path in modules_to_check:
module = resolver.get_module(ModulePath(module_path))
status = "✓ exists" if module.exists else "✗ not found"
module_name = '.'.join(module_path)
print(f" {module_name}: {status}")
if module.exists:
print(f" ({len(module.names)} names)")from typeshed_client import Resolver, ModulePath, NameInfo, ImportedName, OverloadedName
import ast
def index_module(resolver: Resolver, module_path: ModulePath):
"""Build an index of all names in a module."""
module = resolver.get_module(module_path)
if not module.exists:
return None
index = {
'functions': [],
'classes': [],
'constants': [],
'imports': [],
'overloaded': [],
'exported_count': 0,
'private_count': 0,
}
for name, info in module.names.items():
# Count export status
if info.is_exported:
index['exported_count'] += 1
else:
index['private_count'] += 1
# Categorize by type
if isinstance(info.ast, ast.FunctionDef):
index['functions'].append(name)
elif isinstance(info.ast, ast.ClassDef):
index['classes'].append(name)
elif isinstance(info.ast, ImportedName):
index['imports'].append(name)
elif isinstance(info.ast, OverloadedName):
index['overloaded'].append(name)
else:
index['constants'].append(name)
return index
resolver = Resolver()
index = index_module(resolver, ModulePath(('typing',)))
if index:
print("typing module index:")
print(f" Functions: {len(index['functions'])}")
print(f" Classes: {len(index['classes'])}")
print(f" Constants: {len(index['constants'])}")
print(f" Imports: {len(index['imports'])}")
print(f" Overloaded: {len(index['overloaded'])}")
print(f" Exported: {index['exported_count']}")
print(f" Private: {index['private_count']}")
else:
print("Could not index typing module")from typeshed_client import Resolver, ModulePath
def resolve_all_names(resolver: Resolver, module_path: ModulePath):
"""Resolve all names in a module."""
module = resolver.get_module(module_path)
if not module.exists:
return {}
resolved = {}
for name in module.names.keys():
result = module.get_name(name, resolver)
resolved[name] = {
'result': result,
'type': type(result).__name__ if result else 'None'
}
return resolved
resolver = Resolver()
all_resolved = resolve_all_names(resolver, ModulePath(('collections',)))
print(f"Resolved {len(all_resolved)} names from collections:")
# Show first 10
for name, details in list(all_resolved.items())[:10]:
print(f" {name}: {details['type']}")from typeshed_client import Resolver, ModulePath, ImportedName
def find_dependencies(resolver: Resolver, module_path: ModulePath):
"""Find all modules that a given module depends on."""
module = resolver.get_module(module_path)
if not module.exists:
return set()
dependencies = set()
for name, info in module.names.items():
if isinstance(info.ast, ImportedName):
dep_module = '.'.join(info.ast.module_name)
dependencies.add(dep_module)
return dependencies
resolver = Resolver()
deps = find_dependencies(resolver, ModulePath(('collections',)))
print(f"collections depends on:")
for dep in sorted(deps):
print(f" - {dep}")from typeshed_client import Resolver, get_search_context, NameInfo, ImportedInfo
import ast
def compare_name_across_versions(name: str, versions):
"""Compare how a name is defined across Python versions."""
results = {}
for version in versions:
ctx = get_search_context(version=version)
resolver = Resolver(search_context=ctx)
result = resolver.get_fully_qualified_name(name)
# Extract type of definition
if result is None:
results[version] = "Not found"
elif isinstance(result, ModulePath):
results[version] = "Module"
elif isinstance(result, NameInfo):
results[version] = type(result.ast).__name__
elif isinstance(result, ImportedInfo):
results[version] = f"Imported ({type(result.info.ast).__name__})"
else:
results[version] = type(result).__name__
return results
# Compare typing.Literal across versions
versions = [(3, 7), (3, 8), (3, 9), (3, 10), (3, 11), (3, 12)]
comparison = compare_name_across_versions('typing.Literal', versions)
print("typing.Literal across Python versions:")
for version, status in comparison.items():
print(f" Python {version[0]}.{version[1]}: {status}")from typeshed_client import Resolver, NameInfo, ImportedInfo, OverloadedName
import ast
def extract_function_signature(resolver: Resolver, qualified_name: str):
"""Extract function signature(s) from a resolved name."""
result = resolver.get_fully_qualified_name(qualified_name)
# Extract NameInfo
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
if not info:
return None
signatures = []
# Handle overloaded functions
if isinstance(info.ast, OverloadedName):
for defn in info.ast.definitions:
if isinstance(defn, ast.FunctionDef):
# Build signature string
args = [f"{arg.arg}" for arg in defn.args.args]
ret = "Any"
if defn.returns:
ret = ast.unparse(defn.returns)
sig = f"def {defn.name}({', '.join(args)}) -> {ret}"
signatures.append(sig)
elif isinstance(info.ast, ast.FunctionDef):
# Single function
defn = info.ast
args = [f"{arg.arg}" for arg in defn.args.args]
ret = "Any"
if defn.returns:
ret = ast.unparse(defn.returns)
sig = f"def {defn.name}({', '.join(args)}) -> {ret}"
signatures.append(sig)
return signatures
resolver = Resolver()
# Test with a function that may have overloads
test_names = ['typing.cast', 'typing.overload', 'collections.OrderedDict']
for name in test_names:
sigs = extract_function_signature(resolver, name)
if sigs:
print(f"\n{name}:")
for i, sig in enumerate(sigs, 1):
print(f" {i}. {sig}")
else:
print(f"\n{name}: No signatures (may not be a function)")from typeshed_client import Resolver, NameInfo, ImportedInfo
def is_name_accessible(resolver: Resolver, qualified_name: str) -> bool:
"""Check if a name is accessible (exists and is exported)."""
result = resolver.get_fully_qualified_name(qualified_name)
if result is None:
return False
# Modules are always accessible
if isinstance(result, ModulePath):
return True
# Extract NameInfo
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
return info is not None and info.is_exported
resolver = Resolver()
# Check some names
names_to_check = [
'typing.List',
'typing._SpecialForm', # Private
'collections.OrderedDict',
'nonexistent.Module',
]
print("Name accessibility check:")
for name in names_to_check:
accessible = is_name_accessible(resolver, name)
status = "✓ accessible" if accessible else "✗ not accessible"
print(f" {name}: {status}")from typeshed_client import Resolver, NameInfo, ImportedInfo
import ast
def resolve_attribute_chain(resolver: Resolver, base_name: str, attributes: list[str]):
"""Resolve a chain of attribute accesses like 'module.Class.method'."""
# Start with the base name
result = resolver.get_fully_qualified_name(base_name)
if result is None:
return None
# Follow each attribute
for attr in attributes:
# Extract NameInfo
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
else:
return None # Can't follow attributes on modules
# Check child_nodes for the attribute
if info.child_nodes and attr in info.child_nodes:
result = info.child_nodes[attr]
else:
return None # Attribute not found
return result
resolver = Resolver()
# Resolve typing.TypedDict.__init__
result = resolve_attribute_chain(resolver, 'typing.TypedDict', ['__init__'])
if result:
print(f"✓ Found typing.TypedDict.__init__: {type(result).__name__}")
if isinstance(result, NameInfo):
print(f" Exported: {result.is_exported}")
else:
print("✗ typing.TypedDict.__init__ not found")
# Try another chain
result = resolve_attribute_chain(resolver, 'collections.OrderedDict', ['__setitem__'])
if result:
print(f"✓ Found collections.OrderedDict.__setitem__")
else:
print("✗ collections.OrderedDict.__setitem__ not accessible")The resolver follows this strategy when resolving names:
This strategy handles complex import chains and re-exports correctly, following the chain until reaching the original definition.
The Resolver maintains internal caches:
Module cache: Parsed modules are cached to avoid re-parsing
Name cache: Resolved names within modules are cached to avoid redundant lookups
Best practices:
Resolver instance for multiple lookupsfrom typeshed_client import Resolver
# GOOD: Reuse resolver for caching benefit
resolver = Resolver()
for name in ['typing.List', 'typing.Dict', 'typing.Set']:
result = resolver.get_fully_qualified_name(name) # Benefits from caching
# BAD: Create new resolver each time (no caching benefit)
for name in ['typing.List', 'typing.Dict', 'typing.Set']:
resolver = Resolver() # Wasteful, no caching benefit
result = resolver.get_fully_qualified_name(name)Performance Characteristics:
# CORRECT - efficient caching
resolver = Resolver()
result1 = resolver.get_fully_qualified_name('typing.List')
result2 = resolver.get_fully_qualified_name('typing.Dict')
result3 = resolver.get_fully_qualified_name('typing.Set')
# INCORRECT - no caching benefit
resolver1 = Resolver()
result1 = resolver1.get_fully_qualified_name('typing.List')
resolver2 = Resolver() # Loses all caches
result2 = resolver2.get_fully_qualified_name('typing.Dict')isinstance() to check if result is ModulePath, ImportedInfo, NameInfo, or None# CORRECT - explicit type checking
result = resolver.get_fully_qualified_name('collections.OrderedDict')
if result is None:
print("Not found")
elif isinstance(result, ModulePath):
print("It's a module")
elif isinstance(result, ImportedInfo):
print(f"Imported from {'.'.join(result.source_module)}")
elif isinstance(result, NameInfo):
print("Defined directly")
# INCORRECT - assumes result is NameInfo
result = resolver.get_fully_qualified_name('collections.OrderedDict')
print(result.name) # AttributeError if result is ModulePath or None# CORRECT - extract NameInfo from ImportedInfo
from typeshed_client import ImportedInfo, NameInfo
result = resolver.get_fully_qualified_name('some.name')
info = None
if isinstance(result, NameInfo):
info = result
elif isinstance(result, ImportedInfo):
info = result.info
if info:
# Now safe to access NameInfo fields
print(f"Name: {info.name}")
print(f"Exported: {info.is_exported}")# CORRECT - type guards
import ast
if isinstance(result, (NameInfo, ImportedInfo)):
info = result.info if isinstance(result, ImportedInfo) else result
if isinstance(info.ast, ast.ClassDef):
# Safe to access ClassDef attributes
print(f"Class with {len(info.ast.bases)} bases")
# INCORRECT - no type guards
info = result.info # AttributeError if result is None or ModulePath
print(info.ast.bases) # May fail if ast is not ClassDefexists attribute before accessing names# CORRECT
module = resolver.get_module(ModulePath(('some_module',)))
if module.exists:
print(f"Found {len(module.names)} names")
else:
print("Module doesn't exist")
# MISLEADING - module.names is empty dict even if module doesn't exist
module = resolver.get_module(ModulePath(('nonexistent',)))
print(f"Names: {len(module.names)}") # Prints 0, but module doesn't exist# CORRECT - follow imports recursively
from typeshed_client import ImportedInfo, ImportedName
def follow_to_source(resolver, qualified_name):
result = resolver.get_fully_qualified_name(qualified_name)
while isinstance(result, ImportedInfo):
if isinstance(result.info.ast, ImportedName):
# Follow to source
result = resolver.get_name(
result.info.ast.module_name,
result.info.ast.name
)
else:
# Found the original
break
return resultSearchContext when resolving version-specific names# CORRECT - version-specific resolvers
ctx_39 = get_search_context(version=(3, 9))
ctx_312 = get_search_context(version=(3, 12))
resolver_39 = Resolver(search_context=ctx_39)
resolver_312 = Resolver(search_context=ctx_312)
# Check availability in each version
result_39 = resolver_39.get_fully_qualified_name('typing.TypeGuard')
result_312 = resolver_312.get_fully_qualified_name('typing.TypeGuard')# CORRECT - batch operations with same resolver
resolver = Resolver()
names = ['typing.List', 'typing.Dict', 'typing.Tuple', 'typing.Set']
results = {name: resolver.get_fully_qualified_name(name) for name in names}
# INCORRECT - creates resolver inside loop
for name in names:
resolver = Resolver() # New cache each iteration
result = resolver.get_fully_qualified_name(name)# CORRECT - check for None
result = resolver.get_fully_qualified_name('maybe.exists')
if result is not None:
# Safe to use result
print(f"Found: {type(result).__name__}")
else:
print("Not found")
# INCORRECT - assumes result exists
result = resolver.get_fully_qualified_name('maybe.exists')
print(result.name) # AttributeError if result is None# CORRECT - separate resolver per thread
import threading
def process_in_thread(names):
resolver = Resolver() # Thread-local resolver
for name in names:
result = resolver.get_fully_qualified_name(name)
# Process result
# INCORRECT - sharing resolver
resolver = Resolver() # Shared
def bad_thread_func(names):
for name in names:
result = resolver.get_fully_qualified_name(name) # Not thread-safe