tessl install tessl/pypi-typeshed-client@2.8.4A library for accessing stubs in typeshed.
Functions for locating stub files for Python modules. The finder module implements PEP 561 import resolution ordering, supporting typeshed stubs, stub packages (packages ending in -stubs), and inline stubs within regular packages.
| Function | Return Type | Can Return None? |
|---|---|---|
get_stub_file(module_name) | Optional[Path] | Yes |
get_stub_ast(module_name) | Optional[ast.Module] | Yes |
get_all_stub_files() | Iterable[tuple[str, Path]] | No (but may be empty) |
find_typeshed() | Path | No |
parse_stub_file(path) | ast.Module | No (raises on error) |
Returns the path to a module's stub file.
def get_stub_file(module_name: str, *, search_context: Optional[SearchContext] = None) -> Optional[Path]:
"""
Return the path to the stub file for this module, if any.
Args:
module_name (str): Module name as a dotted string (e.g., 'typing', 'collections.abc')
search_context (SearchContext, optional): Context for finding stubs. Uses default if None.
Returns:
Path: Path to the stub file, or None if no stub is found
Example:
path = get_stub_file('typing')
# Returns: Path('/path/to/typeshed/stdlib/typing.pyi')
"""Return Value Details:
Path object pointing to .pyi stub filePath to .py file if allow_py_files=True in SearchContext and no .pyi existsNone if module not found in any search locationPerformance: O(n) where n is number of directories in search path
Returns the parsed AST for a module's stub file.
def get_stub_ast(module_name: str, *, search_context: Optional[SearchContext] = None) -> Optional[ast.Module]:
"""
Return the AST for the stub for the given module name.
Args:
module_name (str): Module name as a dotted string
search_context (SearchContext, optional): Context for finding stubs. Uses default if None.
Returns:
ast.Module: Parsed AST of the stub file, or None if no stub is found
Example:
ast_tree = get_stub_ast('collections')
# Returns: ast.Module object with the parsed stub
"""Return Value Details:
ast.Module object from Python's ast moduleNone if module not foundSyntaxError if stub file has invalid Python syntaxPerformance: File I/O + parsing cost on first call; consider caching result if needed
Returns paths to all available stub files for the configured Python version.
def get_all_stub_files(search_context: Optional[SearchContext] = None) -> Iterable[tuple[str, Path]]:
"""
Return paths to all stub files for a given Python version.
Args:
search_context (SearchContext, optional): Context for finding stubs. Uses default if None.
Returns:
Iterable[tuple[str, Path]]: Pairs of (module name, module path)
Example:
for module_name, path in get_all_stub_files():
print(f"{module_name}: {path}")
# Prints: typing: /path/to/typeshed/stdlib/typing.pyi
# collections: /path/to/typeshed/stdlib/collections/__init__.pyi
# ...
"""Return Value Details:
(module_name: str, stub_path: Path)'collections.abc')None, but iterator may be emptyPerformance: Scans all directories in search path; may be slow for large environments
Usage Note: Results are not cached; convert to list if multiple iterations needed
This function is deprecated and may be removed in a future version. The function is marked with the @deprecated decorator for type checkers and IDEs, but does not emit runtime warnings when called.
@deprecated("This function is not useful with the current layout of typeshed. It may be removed from a future version of typeshed-client.")
def get_search_path(typeshed_dir: Path, pyversion: tuple[int, int]) -> tuple[Path, ...]:
"""
Returns search paths for given typeshed and Python version.
This function is not useful with the current layout of typeshed.
Marked as deprecated for type checkers but does not emit runtime warnings.
Args:
typeshed_dir (Path): Path to typeshed directory
pyversion (tuple[int, int]): Python version as (major, minor)
Returns:
tuple[Path, ...]: Tuple of search paths
"""Agent Guidance: Avoid using this function; use get_search_context() instead for configuring search paths.
The finder follows PEP 561 import resolution ordering:
stdlib/stubs/-stubs on the search path
requests-stubs for requests module.pyi files in regular packages
package/module.pyi or package/py.typed marker.py files if allow_py_files is True
.pyi stub existsVersion Handling: Version-specific stubs are handled using the VERSIONS file in typeshed, which maps modules to minimum and maximum Python versions.
Platform Handling: Platform-specific stubs are determined by directory structure and conditional imports.
from typeshed_client import get_stub_file
# Find typing module stub
path = get_stub_file('typing')
if path:
print(f"Found: {path}") # /path/to/typeshed/stdlib/typing.pyi
else:
print("Stub not found")
# Find collections.abc module stub
path = get_stub_file('collections.abc')
if path:
print(f"Found: {path}") # /path/to/typeshed/stdlib/collections/abc.pyi
# Try to find a non-existent module
path = get_stub_file('nonexistent_module')
print(path) # NoneKey Points:
None before using the pathopen() or Path methods'collections.abc')from typeshed_client import get_stub_ast
import ast
# Get the AST for the typing module
tree = get_stub_ast('typing')
if tree:
# Walk the AST to find all class definitions
classes = []
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
classes.append(node.name)
print(f"Found classes: {classes}")
# Count function definitions
func_count = sum(1 for node in ast.walk(tree) if isinstance(node, ast.FunctionDef))
print(f"Found {func_count} function definitions")
else:
print("Stub not found")Key Points:
ast.Module objectast.walk() to traverse all nodesisinstance()None casefrom typeshed_client import get_all_stub_files
# Get all available stubs
all_stubs = list(get_all_stub_files())
print(f"Total stubs available: {len(all_stubs)}")
# Filter to specific modules
collections_stubs = []
for module_name, path in get_all_stub_files():
if module_name.startswith('collections'):
collections_stubs.append((module_name, path))
print(f"{module_name}: {path}")
print(f"\nFound {len(collections_stubs)} collections-related stubs")Key Points:
'collections.abc')from pathlib import Path
from typeshed_client import get_search_context, get_stub_file
# Use a custom typeshed location
custom_typeshed = Path('/custom/typeshed')
if custom_typeshed.exists():
ctx = get_search_context(typeshed=custom_typeshed)
path = get_stub_file('typing', search_context=ctx)
if path:
print(f"Using custom typeshed: {path}")
# Verify it's from our custom location
if str(custom_typeshed) in str(path):
print("✓ Using correct typeshed")
else:
print("Custom typeshed not found")Key Points:
from typeshed_client import get_search_context, get_stub_file
# Find stubs for Python 3.9
ctx_39 = get_search_context(version=(3, 9))
path_39 = get_stub_file('asyncio', search_context=ctx_39)
# Find stubs for Python 3.11
ctx_311 = get_search_context(version=(3, 11))
path_311 = get_stub_file('asyncio', search_context=ctx_311)
# The paths may be the same, but version checks inside stubs will differ
if path_39 and path_311:
print(f"Python 3.9: {path_39}")
print(f"Python 3.11: {path_311}")
print(f"Same file: {path_39 == path_311}")
# Check for version-specific modules
tomllib_39 = get_stub_file('tomllib', search_context=ctx_39)
tomllib_311 = get_stub_file('tomllib', search_context=ctx_311)
print(f"tomllib in 3.9: {tomllib_39 is not None}") # False (added in 3.11)
print(f"tomllib in 3.11: {tomllib_311 is not None}") # TrueKey Points:
from pathlib import Path
from typeshed_client import get_search_context, get_stub_file
# Search for stubs in a virtual environment
venv_site_packages = Path('./venv/lib/python3.11/site-packages')
if venv_site_packages.exists():
ctx = get_search_context(search_path=[venv_site_packages])
# Try to find a third-party package stub
path = get_stub_file('requests', search_context=ctx)
if path:
print(f"Found requests stub: {path}")
# Check if it's a stub package
if '-stubs' in str(path):
print("✓ Using stub package")
else:
print("No stub found for requests")
# Find inline stub
path = get_stub_file('mypy', search_context=ctx)
if path:
print(f"Found mypy stub: {path}")
if path.suffix == '.pyi':
print("✓ Using inline stub (.pyi)")
else:
print("Virtual environment not found")Key Points:
{package}-stubs.pyi files alongside .py filesfrom typeshed_client import get_stub_file
modules_to_check = ['typing', 'collections', 'asyncio', 'nonexistent']
results = {}
for module_name in modules_to_check:
path = get_stub_file(module_name)
results[module_name] = path is not None
status = "✓ Found" if path else "✗ Not found"
print(f"{module_name}: {status}")
# Summary
found_count = sum(1 for exists in results.values() if exists)
print(f"\nFound {found_count}/{len(modules_to_check)} modules")Key Points:
path is not Nonefrom typeshed_client import get_stub_file, get_stub_ast
from pathlib import Path
module_name = 'some_module'
# Step 1: Check if stub file exists
stub_path = get_stub_file(module_name)
if stub_path is None:
print(f"Warning: No stub found for {module_name}")
else:
# Step 2: Verify the file actually exists
from typeshed_client.finder import safe_exists
if not safe_exists(stub_path):
print(f"Error: Stub path {stub_path} does not exist")
else:
# Step 3: Try to parse it
try:
from typeshed_client.finder import parse_stub_file
tree = parse_stub_file(stub_path)
print(f"✓ Successfully parsed {module_name} stub")
# Verify it's a Module node
import ast
if isinstance(tree, ast.Module):
print(f"✓ Valid AST Module with {len(tree.body)} top-level statements")
except SyntaxError as e:
print(f"✗ Syntax error in stub: {e}")
print(f" Line {e.lineno}: {e.text}")
except Exception as e:
print(f"✗ Error parsing stub: {e}")Key Points:
None firstsafe_exists()SyntaxError for malformed stubsReturns the path to the bundled typeshed directory.
def find_typeshed() -> Path:
"""
Return the path to the bundled typeshed directory.
Returns:
Path: Path to the bundled typeshed directory included with typeshed_client
Example:
from typeshed_client.finder import find_typeshed
typeshed_path = find_typeshed()
print(typeshed_path) # Path to bundled typeshed
"""Return Value Details:
Path object (never None)typeshed/stdlib/ and typeshed/stubs/Usage Example:
from typeshed_client.finder import find_typeshed
typeshed_path = find_typeshed()
print(f"Bundled typeshed: {typeshed_path}")
# Explore structure
stdlib_dir = typeshed_path / 'stdlib'
stubs_dir = typeshed_path / 'stubs'
print(f"Standard library stubs: {stdlib_dir.exists()}")
print(f"Third-party stubs: {stubs_dir.exists()}")Parses a stub file at the given path into an AST.
def parse_stub_file(path: Path) -> ast.Module:
"""
Parse a stub file into an AST.
Args:
path (Path): Path to the stub file to parse
Returns:
ast.Module: Parsed AST of the stub file
Raises:
SyntaxError: If the stub file contains invalid Python syntax
FileNotFoundError: If the file does not exist
IOError: If the file cannot be read
Example:
from pathlib import Path
from typeshed_client.finder import parse_stub_file
stub_path = Path('/path/to/stub.pyi')
tree = parse_stub_file(stub_path)
# Returns: ast.Module object
"""Return Value Details:
ast.Module object (never None)None)ast.parse() in stub modeError Handling:
from pathlib import Path
from typeshed_client.finder import parse_stub_file
stub_path = Path('/path/to/stub.pyi')
try:
tree = parse_stub_file(stub_path)
print(f"✓ Parsed successfully")
except FileNotFoundError:
print(f"✗ File not found: {stub_path}")
except SyntaxError as e:
print(f"✗ Syntax error: {e}")
except Exception as e:
print(f"✗ Unexpected error: {e}")These utility functions provide safe file system operations that handle errors gracefully.
def safe_exists(path: Path) -> bool:
"""
Check if a path exists, returning False on OSError.
Args:
path (Path): Path to check
Returns:
bool: True if path exists, False otherwise or on error
"""Behavior:
True if path existsFalse if path doesn't existFalse on permission errors or other OSErrorsdef safe_is_dir(path: Union[Path, os.DirEntry]) -> bool:
"""
Check if a path is a directory, returning False on OSError.
Args:
path (Path | os.DirEntry): Path to check
Returns:
bool: True if path is a directory, False otherwise or on error
"""Behavior:
True if path is a directoryFalse if path is not a directoryFalse on errorsPath and os.DirEntry objectsdef safe_is_file(path: Union[Path, os.DirEntry]) -> bool:
"""
Check if a path is a file, returning False on OSError.
Args:
path (Path | os.DirEntry): Path to check
Returns:
bool: True if path is a file, False otherwise or on error
"""Behavior:
True if path is a fileFalse if path is not a fileFalse on errorsPath and os.DirEntry objectsdef safe_scandir(path: os.PathLike[str]) -> Iterable[os.DirEntry]:
"""
Iterate over directory entries, ignoring OSError.
Args:
path (PathLike[str]): Directory path to scan
Returns:
Iterable[os.DirEntry]: Iterator over directory entries
Example:
from typeshed_client.finder import safe_scandir
for entry in safe_scandir('/some/directory'):
print(entry.name)
"""Behavior:
os.DirEntry objectsUsage Example:
from pathlib import Path
from typeshed_client.finder import safe_exists, safe_is_file, safe_is_dir, find_typeshed
# Check if a stub file exists safely
stub_path = Path('/path/to/typing.pyi')
if safe_exists(stub_path):
if safe_is_file(stub_path):
print("✓ Stub file exists and is a file")
elif safe_is_dir(stub_path):
print("✗ Path is a directory, not a file")
else:
print("✗ Stub file not found")
# Get bundled typeshed location
typeshed_dir = find_typeshed()
stdlib_dir = typeshed_dir / 'stdlib'
if safe_exists(stdlib_dir) and safe_is_dir(stdlib_dir):
print(f"✓ Standard library stubs at: {stdlib_dir}")
else:
print("✗ Standard library stubs not found")from typeshed_client.finder import find_typeshed, safe_scandir, safe_is_dir, safe_is_file
# List all directories in typeshed stdlib
typeshed = find_typeshed()
stdlib = typeshed / 'stdlib'
print("Directories in stdlib:")
dir_count = 0
file_count = 0
for entry in safe_scandir(stdlib):
if safe_is_dir(entry):
print(f" 📁 {entry.name}")
dir_count += 1
elif safe_is_file(entry):
file_count += 1
print(f"\nTotal: {dir_count} directories, {file_count} files")Key Points:
safe_scandir() returns os.DirEntry objects, not Pathos.DirEntry objects can be used with safe_is_dir() and safe_is_file()entry.name, full path with entry.pathfrom typeshed_client import get_stub_file, get_search_context
# Find stubs for multiple modules efficiently
ctx = get_search_context() # Create context once
modules = ['typing', 'collections', 'asyncio', 'dataclasses']
stub_paths = {}
for module in modules:
path = get_stub_file(module, search_context=ctx)
if path:
stub_paths[module] = path
print(f"Found {len(stub_paths)} out of {len(modules)} stubs")
for module, path in stub_paths.items():
print(f" {module}: {path.name}")from typeshed_client import get_all_stub_files, get_search_context
from collections import defaultdict
# Check which standard library modules have stubs
ctx = get_search_context()
all_stubs = list(get_all_stub_files(search_context=ctx))
print(f"Total stub files: {len(all_stubs)}")
# Group by top-level module
top_level = defaultdict(int)
for module_name, _ in all_stubs:
top = module_name.split('.')[0]
top_level[top] += 1
print("\nTop-level modules with stubs:")
for module, count in sorted(top_level.items()):
print(f" {module}: {count} file(s)")
# Find largest module families
top_5 = sorted(top_level.items(), key=lambda x: x[1], reverse=True)[:5]
print("\nTop 5 module families:")
for module, count in top_5:
print(f" {module}: {count} files")from typeshed_client import get_search_context, get_stub_ast
import ast
def count_definitions(tree):
"""Count classes and functions in AST."""
classes = sum(1 for n in ast.walk(tree) if isinstance(n, ast.ClassDef))
functions = sum(1 for n in ast.walk(tree) if isinstance(n, ast.FunctionDef))
return classes, functions
# Get ASTs for different Python versions
versions = [(3, 9), (3, 12)]
results = {}
for version in versions:
ctx = get_search_context(version=version)
tree = get_stub_ast('typing', search_context=ctx)
if tree:
classes, functions = count_definitions(tree)
results[version] = {'classes': classes, 'functions': functions}
print(f"Python {version[0]}.{version[1]}: {classes} classes, {functions} functions")
# Compare
if len(results) == 2:
v1, v2 = versions
class_diff = results[v2]['classes'] - results[v1]['classes']
func_diff = results[v2]['functions'] - results[v1]['functions']
print(f"\nChanges from {v1} to {v2}:")
print(f" Classes: {class_diff:+d}")
print(f" Functions: {func_diff:+d}")from typeshed_client import get_stub_file
def has_stub_support(module_name: str) -> bool:
"""Check if a module has type stub support."""
return get_stub_file(module_name) is not None
# Use in validation logic
required_modules = ['typing', 'dataclasses', 'pydantic']
all_available = True
for module in required_modules:
if has_stub_support(module):
print(f"✓ {module} has stub support")
else:
print(f"✗ {module} lacks stub support")
all_available = False
if all_available:
print("\n✓ All required modules have stubs")
else:
print("\n✗ Some required modules lack stubs")from typeshed_client import get_stub_ast
import ast
def get_module_docstring(module_name: str) -> str | None:
"""Extract the docstring from a module's stub."""
tree = get_stub_ast(module_name)
if tree:
return ast.get_docstring(tree)
return None
# Get docstrings from standard library modules
modules = ['typing', 'collections', 'asyncio', 'dataclasses']
for module in modules:
doc = get_module_docstring(module)
if doc:
first_line = doc.split('\n')[0]
print(f"{module}: {first_line}")
else:
print(f"{module}: No docstring")from typeshed_client import get_all_stub_files, get_search_context
def get_package_structure(package_name: str) -> dict:
"""Get all submodules of a package."""
ctx = get_search_context()
structure = {
'submodules': [],
'total': 0
}
prefix = f"{package_name}."
for module_name, path in get_all_stub_files(search_context=ctx):
if module_name == package_name or module_name.startswith(prefix):
structure['submodules'].append(module_name)
structure['total'] += 1
return structure
# Analyze collections package
collections_structure = get_package_structure('collections')
print(f"collections package structure:")
print(f" Total modules: {collections_structure['total']}")
print(f" Submodules:")
for submodule in sorted(collections_structure['submodules']):
print(f" - {submodule}")from typeshed_client import get_stub_file, get_search_context
from pathlib import Path
def detect_stub_type(module_name: str) -> str:
"""Determine what kind of stub a module has."""
path = get_stub_file(module_name)
if path is None:
return "no_stub"
path_str = str(path)
# Check if from typeshed
from typeshed_client.finder import find_typeshed
typeshed_dir = find_typeshed()
if str(typeshed_dir) in path_str:
if 'stdlib' in path_str:
return "typeshed_stdlib"
elif 'stubs' in path_str:
return "typeshed_thirdparty"
# Check if stub package
if '-stubs' in path_str:
return "stub_package"
# Check if inline stub
if path.suffix == '.pyi':
return "inline_pyi"
# Must be .py file (if allow_py_files=True)
if path.suffix == '.py':
return "python_source"
return "unknown"
# Test with various modules
modules = ['typing', 'collections', 'requests', 'mypy']
for module in modules:
stub_type = detect_stub_type(module)
print(f"{module}: {stub_type}")get_stub_file() and get_stub_ast() return None when a module is not found# CORRECT
path = get_stub_file('module')
if path is not None:
# Use path
pass
# INCORRECT
path = get_stub_file('module')
with open(path) as f: # May raise TypeError if path is None
content = f.read()SearchContext once and reuse it across multiple calls for consistency# CORRECT - efficient
ctx = get_search_context(version=(3, 11))
path1 = get_stub_file('typing', search_context=ctx)
path2 = get_stub_file('collections', search_context=ctx)
# LESS EFFICIENT
path1 = get_stub_file('typing', search_context=get_search_context(version=(3, 11)))
path2 = get_stub_file('collections', search_context=get_search_context(version=(3, 11)))safe_exists(), safe_is_file(), etc. over direct path operations to avoid exceptions# CORRECT - handles errors gracefully
from typeshed_client.finder import safe_exists, safe_is_file
if safe_exists(path) and safe_is_file(path):
# Use path
pass
# MAY RAISE - OSError on permission issues
if path.exists() and path.is_file():
# Use path
pass# CORRECT - check each version
versions = [(3, 9), (3, 10), (3, 11)]
availability = {}
for version in versions:
ctx = get_search_context(version=version)
path = get_stub_file('tomllib', search_context=ctx)
availability[version] = path is not None
print(f"tomllib added in: {[v for v, avail in availability.items() if avail]}")get_stub_file(), verify it exists with safe_exists()# CORRECT - defensive check
from typeshed_client.finder import safe_exists
path = get_stub_file('module')
if path is not None and safe_exists(path):
# Now safe to use
pass# Understanding resolution:
# 1. Typeshed (stdlib or stubs/)
# 2. Stub packages (package-stubs)
# 3. Inline stubs (.pyi files)
# 4. Python files (.py if allow_py_files=True)
# To find what type of stub was found:
path = get_stub_file('requests')
if path:
if '-stubs' in str(path):
print("Using stub package")
elif path.suffix == '.pyi':
print("Using inline stub")
elif path.suffix == '.py':
print("Using Python source")SearchContext once and reuse it# CORRECT - batch efficiently
ctx = get_search_context()
modules = ['typing', 'collections', 'asyncio', 'dataclasses']
results = {}
for module in modules:
results[module] = get_stub_file(module, search_context=ctx)parse_stub_file() calls in try-except to handle syntax errors gracefully# CORRECT - handle errors
from typeshed_client.finder import parse_stub_file
try:
tree = parse_stub_file(path)
# Use tree
except SyntaxError as e:
print(f"Invalid stub syntax: {e}")
except Exception as e:
print(f"Error parsing: {e}")get_all_stub_files() returns an iterator; convert to list for multiple passes# CORRECT - convert once
all_stubs = list(get_all_stub_files())
print(f"Total: {len(all_stubs)}")
for module_name, path in all_stubs:
# Process
pass
# INCORRECT - iterator exhausted after first loop
all_stubs = get_all_stub_files() # Iterator
for module_name, path in all_stubs:
pass
for module_name, path in all_stubs: # Empty, iterator exhausted
pass__init__.pyi# Module can be:
# - Single file: typing.pyi
# - Package: collections/__init__.pyi
# - Submodule: collections/abc.pyi
path = get_stub_file('collections')
if path:
if path.name == '__init__.pyi':
print(f"Package: {path.parent.name}")
else:
print(f"Module: {path.stem}")