Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/typeshed-client@2.8.x

docs

index.md
tile.json

tessl/pypi-typeshed-client

tessl install tessl/pypi-typeshed-client@2.8.4

A library for accessing stubs in typeshed.

search-context.mddocs/reference/

Search Context Configuration

The search context provides configuration for customizing stub finding and parsing behavior. It controls which typeshed directory to use, where to search for stubs, what Python version to target, and how to handle parsing errors.

Quick Reference

Function/MethodReturn TypeCan Return None?
get_search_context(...)SearchContextNo
SearchContext.is_python2()boolNo

Raises: ValueError if both python_executable and search_path are provided

Capabilities

Get Search Context

Creates a configured SearchContext for stub operations.

def get_search_context(
    *,
    typeshed: Optional[Path] = None,
    search_path: Optional[Sequence[Path]] = None,
    python_executable: Optional[str] = None,
    version: Optional[PythonVersion] = None,
    platform: str = sys.platform,
    raise_on_warnings: bool = False,
    allow_py_files: bool = False
) -> SearchContext:
    """
    Return a context for finding stubs.

    This context can be passed to other functions in typeshed_client to customize
    stub finding behavior.

    Args:
        typeshed (Path, optional): Path to typeshed directory. If not provided,
            uses the bundled version of typeshed.
        search_path (Sequence[Path], optional): List of directories to search for stubs.
            If not provided, sys.path is used.
        python_executable (str, optional): Path to Python executable for determining
            search_path. Default is sys.executable. Must be a valid path to an existing
            Python interpreter. Raises ValueError if both python_executable and search_path
            are provided.
        version (PythonVersion, optional): Python version as (major, minor) tuple,
            e.g. (3, 11). Used for interpreting sys.version_info checks in stubs.
            Default is current Python version.
        platform (str, optional): Platform string for interpreting sys.platform checks.
            Default is sys.platform of current process.
        raise_on_warnings (bool, optional): If True, raise InvalidStub for any warnings
            encountered by the parser. Default is False (warnings are logged).
        allow_py_files (bool, optional): If True, search for .py files in addition to
            .pyi stub files. Useful for typed packages with inline stubs. Default is False.

    Returns:
        SearchContext: Configured context for stub operations

    Raises:
        ValueError: If both python_executable and search_path are provided

    Example:
        # Use default context
        ctx = get_search_context()

        # Configure for specific Python version
        ctx = get_search_context(version=(3, 9))

        # Use custom typeshed location
        ctx = get_search_context(typeshed=Path('/custom/typeshed'))

        # Allow searching .py files
        ctx = get_search_context(allow_py_files=True)
    """

Return Value Details:

  • Always returns a SearchContext object (never None)
  • SearchContext is immutable (NamedTuple)
  • Can be safely reused across multiple operations
  • Thread-safe (immutable)

Error Cases:

  • Raises ValueError if both python_executable and search_path provided
  • May raise FileNotFoundError if python_executable doesn't exist
  • Invalid typeshed path will cause stub lookup failures (not immediate error)

SearchContext Class

The SearchContext is a NamedTuple containing all configuration options.

class SearchContext(NamedTuple):
    """
    Configuration context for stub finding operations.

    Attributes:
        typeshed (Path): Path to typeshed directory
        search_path (Sequence[Path]): Directories to search for stubs
        version (PythonVersion): Python version as (major, minor) tuple
        platform (str): Platform string for sys.platform checks
        raise_on_warnings (bool): Whether to raise exceptions on parser warnings
        allow_py_files (bool): Whether to search .py files in addition to .pyi files
    """
    typeshed: Path
    search_path: Sequence[Path]
    version: PythonVersion
    platform: str
    raise_on_warnings: bool = False
    allow_py_files: bool = False

    def is_python2(self) -> bool:
        """
        Check if the configured version is Python 2.

        Returns:
            bool: True if version[0] == 2, False otherwise

        Example:
            ctx = get_search_context(version=(2, 7))
            print(ctx.is_python2())  # True

            ctx = get_search_context(version=(3, 11))
            print(ctx.is_python2())  # False
        """

Attributes:

  • typeshed (Path): Path to typeshed directory

    • Contains stdlib/ and stubs/ subdirectories
    • Default: Bundled typeshed
    • Must exist on filesystem
  • search_path (Sequence[Path]): List of directories to search

    • Used for finding stub packages and inline stubs
    • Default: sys.path
    • Order matters: earlier directories take precedence
  • version (PythonVersion): Python version tuple

    • Format: (major, minor) e.g., (3, 11)
    • Affects conditional stub parsing
    • Default: Current Python version
  • platform (str): Platform string

    • Format: Same as sys.platform
    • Common values: 'linux', 'win32', 'darwin'
    • Affects platform-specific stub parsing
    • Default: Current platform
  • raise_on_warnings (bool): Error handling mode

    • True: Raise InvalidStub on parser warnings
    • False: Log warnings, continue parsing
    • Default: False
  • allow_py_files (bool): Python file support

    • True: Search .py files if no .pyi found
    • False: Only search .pyi stub files
    • Default: False

Immutability: SearchContext is immutable (NamedTuple); create new instance for different configuration

Thread Safety: Safe to share across threads

Configuration Options

Typeshed Path

Controls which typeshed directory to use for standard library stubs.

Default: Bundled typeshed included with typeshed_client

Custom usage:

from pathlib import Path
from typeshed_client import get_search_context, get_stub_file

# Use a custom typeshed checkout
custom_typeshed = Path('/home/user/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 custom location
        if str(custom_typeshed) in str(path):
            print("✓ Correct typeshed in use")
else:
    print("Custom typeshed not found")

Requirements:

  • Must have stdlib/ subdirectory for standard library stubs
  • May have stubs/ subdirectory for third-party stubs
  • Must follow typeshed directory structure

Search Path

List of directories to search for stubs, including stub packages and inline stubs.

Default: sys.path of current Python interpreter

Custom usage:

from pathlib import Path
from typeshed_client import get_search_context

# Search specific directories
ctx = get_search_context(search_path=[
    Path('/project/stubs'),
    Path('/project/venv/lib/python3.11/site-packages'),
])

Resolution Order:

  1. Directories are searched in order
  2. First match wins
  3. Stub packages (*-stubs) found in these directories
  4. Inline stubs (.pyi files) found in these directories

Performance: More directories = slower stub lookup; keep list minimal

Python Executable

Specifies which Python executable to use for determining the search path.

Default: sys.executable (current Python interpreter)

Usage:

from typeshed_client import get_search_context

# Use a different Python interpreter's path
ctx = get_search_context(python_executable='/usr/bin/python3.9')

Important Notes:

  • The path must point to a valid, existing Python interpreter
  • Raises ValueError if both python_executable and search_path are provided (cannot use both simultaneously)
  • Executable is invoked to determine its sys.path
  • Useful for analyzing stubs for different Python environments

Error Handling:

from typeshed_client import get_search_context

try:
    ctx = get_search_context(python_executable='/nonexistent/python')
except (ValueError, FileNotFoundError) as e:
    print(f"Error: {e}")
    # Fall back to default
    ctx = get_search_context()

Python Version

Target Python version for version-specific stub handling.

Default: Current Python version (sys.version_info[:2])

Usage:

from typeshed_client import get_search_context, get_stub_names

# Parse stubs as if running Python 3.9
ctx = get_search_context(version=(3, 9))
names = get_stub_names('typing', search_context=ctx)

# Stubs with "if sys.version_info >= (3, 10):" will exclude those definitions
if names:
    has_uniontype = 'UnionType' in names  # False for Python 3.9
    print(f"UnionType available: {has_uniontype}")

Affects:

  • Conditional definitions based on sys.version_info
  • Module availability (some modules only exist in certain versions)
  • Stub content (version-specific APIs)

Examples:

# Check feature availability across versions
versions = [(3, 8), (3, 9), (3, 10), (3, 11)]
for version in versions:
    ctx = get_search_context(version=version)
    names = get_stub_names('typing', search_context=ctx)
    if names:
        available = [name for name in ['Literal', 'TypeGuard', 'TypedDict'] 
                     if name in names]
        print(f"Python {version[0]}.{version[1]}: {available}")

Platform

Target platform for platform-specific stub handling.

Default: sys.platform of current system

Usage:

from typeshed_client import get_search_context, get_stub_names

# Parse stubs as if on Linux
ctx = get_search_context(platform='linux')
names = get_stub_names('os', search_context=ctx)

# Stubs with "if sys.platform == 'win32':" will be excluded
if names:
    has_startfile = 'startfile' in names  # False on Linux
    print(f"os.startfile available: {has_startfile}")

Common platform values:

  • 'linux' - Linux systems
  • 'win32' - Windows (both 32-bit and 64-bit)
  • 'darwin' - macOS
  • 'cygwin' - Cygwin on Windows
  • 'freebsd' - FreeBSD
  • 'openbsd' - OpenBSD

Affects:

  • Conditional definitions based on sys.platform
  • Platform-specific modules and functions
  • OS-specific type signatures

Examples:

# Compare platform-specific APIs
platforms = ['linux', 'win32', 'darwin']
module = 'os'

for platform in platforms:
    ctx = get_search_context(platform=platform)
    names = get_stub_names(module, search_context=ctx)
    if names:
        print(f"\n{platform}:")
        # Check for platform-specific functions
        specific_funcs = []
        if platform == 'win32' and 'startfile' in names:
            specific_funcs.append('startfile')
        if platform in ('linux', 'darwin') and 'fork' in names:
            specific_funcs.append('fork')
        print(f"  Platform-specific: {specific_funcs}")

Raise on Warnings

Controls whether parser warnings are raised as exceptions.

Default: False (warnings are logged)

Usage:

from typeshed_client import get_search_context, get_stub_names
from typeshed_client.parser import InvalidStub

# Enable strict parsing
ctx = get_search_context(raise_on_warnings=True)

try:
    names = get_stub_names('some_module', search_context=ctx)
    if names:
        print(f"✓ Parsed {len(names)} names")
    else:
        print("✗ Module not found")
except InvalidStub as e:
    print(f"✗ Parser warning: {e}")
    if hasattr(e, 'file_path') and e.file_path:
        print(f"  File: {e.file_path}")

When to Use:

  • True: When validating stub quality, testing, strict mode
  • False: When robustness is needed, production use, lenient parsing

Performance: No significant performance impact

Examples:

# Validate multiple modules with strict parsing
from typeshed_client.parser import InvalidStub

ctx_strict = get_search_context(raise_on_warnings=True)
modules = ['typing', 'collections', 'asyncio', 'dataclasses']

results = {'valid': [], 'invalid': []}
for module in modules:
    try:
        names = get_stub_names(module, search_context=ctx_strict)
        if names:
            results['valid'].append(module)
    except InvalidStub as e:
        results['invalid'].append((module, str(e)))

print(f"Valid: {len(results['valid'])}")
print(f"Invalid: {len(results['invalid'])}")
for module, error in results['invalid']:
    print(f"  {module}: {error}")

Allow Python Files

Controls whether to search .py files in addition to .pyi stub files.

Default: False (only search .pyi files)

Usage:

from typeshed_client import get_search_context, get_stub_file

# Search both .pyi and .py files
ctx = get_search_context(allow_py_files=True)
path = get_stub_file('mypackage', search_context=ctx)
if path:
    print(f"Found: {path}")
    if path.suffix == '.py':
        print("✓ Using Python source file (not stub)")
    elif path.suffix == '.pyi':
        print("✓ Using stub file")

Use Cases:

  • Typed packages with inline type annotations (PEP 561)
  • Analyzing actual Python code (not just stubs)
  • Packages without separate stub files
  • Development/testing scenarios

Resolution Order (when allow_py_files=True):

  1. .pyi stub files (preferred)
  2. .py files (if no stub found)

Performance: Minimal impact (only checked if stub not found)

Examples:

# Compare stub vs source file
from pathlib import Path

ctx_stubs_only = get_search_context(allow_py_files=False)
ctx_with_py = get_search_context(allow_py_files=True)

modules = ['typing', 'mypy', 'custom_package']
for module in modules:
    path_stub = get_stub_file(module, search_context=ctx_stubs_only)
    path_any = get_stub_file(module, search_context=ctx_with_py)
    
    print(f"\n{module}:")
    if path_stub:
        print(f"  Stub: {path_stub.name}")
    else:
        print(f"  Stub: Not found")
    
    if path_any:
        file_type = path_any.suffix
        print(f"  With .py: {path_any.name} ({file_type})")
    else:
        print(f"  With .py: Not found")

Usage Examples

Basic Context Creation

from typeshed_client import get_search_context

# Create default context
ctx = get_search_context()

print(f"Version: {ctx.version}")
print(f"Platform: {ctx.platform}")
print(f"Typeshed: {ctx.typeshed}")
print(f"Search path: {len(ctx.search_path)} directories")
print(f"Raise on warnings: {ctx.raise_on_warnings}")
print(f"Allow .py files: {ctx.allow_py_files}")

# Check if Python 2
if ctx.is_python2():
    print("Python 2 context")
else:
    print("Python 3 context")

Version-Specific Analysis

from typeshed_client import get_search_context, get_stub_names

versions = [(3, 8), (3, 9), (3, 10), (3, 11), (3, 12)]
results = {}

print("Analyzing 'typing' module across Python versions:")
for version in versions:
    ctx = get_search_context(version=version)
    names = get_stub_names('typing', search_context=ctx)
    
    if names:
        results[version] = {
            'total': len(names),
            'exported': sum(1 for info in names.values() if info.is_exported),
            'names': set(names.keys())
        }
        print(f"  Python {version[0]}.{version[1]}: {results[version]['total']} names")

# Find names added in newer versions
if (3, 8) in results and (3, 12) in results:
    new_names = results[(3, 12)]['names'] - results[(3, 8)]['names']
    print(f"\nNames added after Python 3.8: {sorted(new_names)[:10]}")

Platform-Specific Stubs

from typeshed_client import get_search_context, get_stub_names

platforms = ['linux', 'win32', 'darwin']
module = 'os'

print(f"Analyzing '{module}' module across platforms:")
platform_names = {}

for platform in platforms:
    ctx = get_search_context(platform=platform)
    names = get_stub_names(module, search_context=ctx)
    
    if names:
        platform_names[platform] = set(names.keys())
        print(f"  {platform}: {len(names)} names")

# Find platform-specific names
if len(platform_names) > 1:
    all_platforms = set.intersection(*platform_names.values())
    print(f"\nCommon to all platforms: {len(all_platforms)} names")
    
    for platform, names in platform_names.items():
        unique = names - all_platforms
        if unique:
            print(f"{platform}-specific: {sorted(unique)[:5]}")

Custom Typeshed Location

from pathlib import Path
from typeshed_client import get_search_context, get_stub_file

# Use development version of typeshed
dev_typeshed = Path('/home/user/git/typeshed')

if dev_typeshed.exists():
    ctx = get_search_context(typeshed=dev_typeshed)
    
    # Find stubs from custom typeshed
    path = get_stub_file('typing', search_context=ctx)
    if path:
        print(f"✓ Using stub from custom typeshed: {path}")
        # Verify it's from our custom location
        if str(dev_typeshed) in str(path):
            print("✓ Confirmed custom typeshed in use")
    else:
        print("✗ Stub not found in custom typeshed")
else:
    print(f"✗ Custom typeshed not found at {dev_typeshed}")

Custom Search Path

from pathlib import Path
from typeshed_client import get_search_context, get_stub_file

# Search project-specific stubs
project_stubs = Path('./stubs')
venv_packages = Path('./venv/lib/python3.11/site-packages')

search_dirs = [d for d in [project_stubs, venv_packages] if d.exists()]

if search_dirs:
    ctx = get_search_context(search_path=search_dirs)
    
    # Find stubs in custom locations
    modules_to_find = ['myproject', 'requests', 'numpy']
    for module in modules_to_find:
        path = get_stub_file(module, search_context=ctx)
        if path:
            # Determine which search directory it came from
            for search_dir in search_dirs:
                if str(search_dir) in str(path):
                    print(f"✓ {module}: found in {search_dir.name}")
                    break
        else:
            print(f"✗ {module}: not found")
else:
    print("✗ No custom search directories exist")

Strict Parsing Mode

from typeshed_client import get_search_context, get_stub_names
from typeshed_client.parser import InvalidStub

# Enable strict parsing
ctx = get_search_context(raise_on_warnings=True)

modules = ['typing', 'collections', 'asyncio', 'dataclasses']
results = {'success': [], 'failed': []}

print("Validating modules with strict parsing:")
for module in modules:
    try:
        names = get_stub_names(module, search_context=ctx)
        if names:
            results['success'].append(module)
            print(f"  ✓ {module}: {len(names)} names")
        else:
            results['failed'].append((module, "Not found"))
            print(f"  ✗ {module}: Not found")
    except InvalidStub as e:
        results['failed'].append((module, str(e)))
        print(f"  ✗ {module}: {e}")

print(f"\nSummary: {len(results['success'])} passed, {len(results['failed'])} failed")

Analyzing Python Source Files

from pathlib import Path
from typeshed_client import get_search_context, get_stub_file, get_stub_names

# Allow searching .py files
ctx = get_search_context(
    allow_py_files=True,
    search_path=[Path('./myproject')]
)

modules = ['mymodule', 'utils', 'config']
print("Searching for modules (including .py files):")

for module in modules:
    path = get_stub_file(module, search_context=ctx)
    if path:
        file_type = "stub" if path.suffix == '.pyi' else "source"
        print(f"  {module}: {path.name} ({file_type})")
        
        # Parse it
        names = get_stub_names(module, search_context=ctx)
        if names:
            exported = sum(1 for info in names.values() if info.is_exported)
            print(f"    Names: {len(names)} ({exported} exported)")
    else:
        print(f"  {module}: Not found")

Combining Multiple Options

from pathlib import Path
from typeshed_client import get_search_context, Resolver

# Create a fully customized context
ctx = get_search_context(
    typeshed=Path('/custom/typeshed'),
    search_path=[
        Path('./stubs'),
        Path('./venv/lib/python3.11/site-packages'),
    ],
    version=(3, 11),
    platform='linux',
    raise_on_warnings=False,  # Lenient for production
    allow_py_files=True
)

print("Custom context configuration:")
print(f"  Typeshed: {ctx.typeshed}")
print(f"  Search paths: {len(ctx.search_path)}")
print(f"  Version: Python {ctx.version[0]}.{ctx.version[1]}")
print(f"  Platform: {ctx.platform}")
print(f"  Raise on warnings: {ctx.raise_on_warnings}")
print(f"  Allow .py files: {ctx.allow_py_files}")

# Use with resolver
resolver = Resolver(search_context=ctx)
result = resolver.get_fully_qualified_name('typing.List')
if result:
    print(f"\n✓ Resolved typing.List: {type(result).__name__}")
else:
    print(f"\n✗ Could not resolve typing.List")

Analyzing Multiple Python Versions

from typeshed_client import get_search_context, get_stub_names

def analyze_versions(module_name: str, versions: list[tuple[int, int]]):
    """Analyze a module across multiple Python versions."""
    results = {}

    print(f"Analyzing '{module_name}' across versions:")
    for version in versions:
        ctx = get_search_context(version=version)
        names = get_stub_names(module_name, search_context=ctx)
        
        if names:
            results[version] = set(names.keys())
            exported = sum(1 for info in names.values() if info.is_exported)
            print(f"  {version[0]}.{version[1]}: {len(names)} total, {exported} exported")
        else:
            results[version] = set()
            print(f"  {version[0]}.{version[1]}: Not found")

    return results

# Compare typing module across versions
versions = [(3, 8), (3, 9), (3, 10), (3, 11), (3, 12)]
results = analyze_versions('typing', versions)

# Analyze progression
print("\nFeature additions:")
prev_names = set()
for version in sorted(results.keys()):
    current_names = results[version]
    if prev_names:
        added = current_names - prev_names
        if added:
            print(f"  Python {version[0]}.{version[1]}: {sorted(added)[:5]}")
    prev_names = current_names

Checking Python 2 Compatibility

from typeshed_client import get_search_context, get_stub_file

# Create Python 2 context
ctx_py2 = get_search_context(version=(2, 7))
ctx_py3 = get_search_context(version=(3, 11))

print("Python 2 vs Python 3:")
print(f"  Python 2 context: {ctx_py2.is_python2()}")
print(f"  Python 3 context: {ctx_py3.is_python2()}")

# Check module availability
modules = ['typing', 'asyncio', '__builtin__', 'builtins']
for module in modules:
    path_py2 = get_stub_file(module, search_context=ctx_py2)
    path_py3 = get_stub_file(module, search_context=ctx_py3)
    
    py2_status = "✓" if path_py2 else "✗"
    py3_status = "✓" if path_py3 else "✗"
    print(f"  {module}: Py2 {py2_status}, Py3 {py3_status}")

Advanced Patterns

Pattern: Context Factory

from pathlib import Path
from typeshed_client import get_search_context, SearchContext

class ContextFactory:
    """Factory for creating SearchContext instances with common configurations."""
    
    def __init__(self, base_search_path: list[Path]):
        self.base_search_path = base_search_path
    
    def for_version(self, version: tuple[int, int]) -> SearchContext:
        """Create context for specific Python version."""
        return get_search_context(
            version=version,
            search_path=self.base_search_path
        )
    
    def for_platform(self, platform: str) -> SearchContext:
        """Create context for specific platform."""
        return get_search_context(
            platform=platform,
            search_path=self.base_search_path
        )
    
    def strict(self) -> SearchContext:
        """Create context with strict parsing."""
        return get_search_context(
            raise_on_warnings=True,
            search_path=self.base_search_path
        )
    
    def lenient(self) -> SearchContext:
        """Create context with lenient parsing and .py file support."""
        return get_search_context(
            raise_on_warnings=False,
            allow_py_files=True,
            search_path=self.base_search_path
        )

# Use the factory
factory = ContextFactory([Path('./stubs'), Path('./venv/lib/python3.11/site-packages')])

ctx_39 = factory.for_version((3, 9))
ctx_311 = factory.for_version((3, 11))
ctx_windows = factory.for_platform('win32')
ctx_strict = factory.strict()
ctx_lenient = factory.lenient()

print("Created 5 contexts with common base configuration")

Pattern: Multi-Version Testing

from typeshed_client import get_search_context, get_stub_names

def test_compatibility(module_name: str, min_version: tuple[int, int], max_version: tuple[int, int]):
    """Test if a module is available across a version range."""
    def next_version(v):
        major, minor = v
        if minor >= 12:  # Arbitrary upper bound for minor version
            return (major + 1, 0)
        return (major, minor + 1)
    
    current = min_version
    available_versions = []
    unavailable_versions = []
    
    while current <= max_version:
        ctx = get_search_context(version=current)
        names = get_stub_names(module_name, search_context=ctx)
        
        if names:
            available_versions.append(current)
        else:
            unavailable_versions.append(current)
        
        current = next_version(current)
        if current > max_version:
            break
    
    return {
        'available': available_versions,
        'unavailable': unavailable_versions
    }

# Test asyncio availability
result = test_compatibility('asyncio', (3, 5), (3, 12))
print(f"asyncio available in: {result['available']}")
if result['unavailable']:
    print(f"asyncio unavailable in: {result['unavailable']}")

Pattern: Platform Detection

from typeshed_client import get_search_context, get_stub_names

def get_platform_specific_names(module_name: str, platforms: list[str] = None):
    """Get names that are platform-specific."""
    if platforms is None:
        platforms = ['linux', 'win32', 'darwin']
    
    all_names = {}
    for platform in platforms:
        ctx = get_search_context(platform=platform)
        names = get_stub_names(module_name, search_context=ctx)
        if names:
            all_names[platform] = set(names.keys())
        else:
            all_names[platform] = set()
    
    # Find platform-specific names
    if len(all_names) > 1:
        # Common to all platforms
        common = set.intersection(*[names for names in all_names.values() if names])
        
        # Platform-specific
        platform_specific = {
            platform: names - common
            for platform, names in all_names.items()
            if names
        }
        
        return {
            'common': common,
            'platform_specific': platform_specific,
            'total_per_platform': {p: len(n) for p, n in all_names.items()}
        }
    
    return {'common': set(), 'platform_specific': {}, 'total_per_platform': {}}

# Find platform-specific names in os module
result = get_platform_specific_names('os')

print(f"os module analysis:")
print(f"  Common to all platforms: {len(result['common'])} names")
for platform, names in result['platform_specific'].items():
    if names:
        print(f"  {platform}-specific: {sorted(names)[:5]}")

Pattern: Dynamic Path Configuration

from pathlib import Path
from typeshed_client import get_search_context
import os

def create_context_from_env():
    """Create SearchContext from environment variables."""
    # Get custom typeshed path from env
    typeshed_path = os.getenv('TYPESHED_PATH')
    typeshed = Path(typeshed_path) if typeshed_path else None
    
    # Get search path from env
    search_path_env = os.getenv('STUB_SEARCH_PATH')
    search_path = None
    if search_path_env:
        search_path = [Path(p) for p in search_path_env.split(os.pathsep)]
    
    # Get version from env
    version_env = os.getenv('PYTHON_VERSION')
    version = None
    if version_env:
        try:
            major, minor = version_env.split('.')
            version = (int(major), int(minor))
        except ValueError:
            print(f"Invalid PYTHON_VERSION: {version_env}")
    
    # Get platform from env
    platform = os.getenv('TARGET_PLATFORM', None)
    
    # Build context
    kwargs = {}
    if typeshed:
        kwargs['typeshed'] = typeshed
    if search_path:
        kwargs['search_path'] = search_path
    if version:
        kwargs['version'] = version
    if platform:
        kwargs['platform'] = platform
    
    return get_search_context(**kwargs)

# Use environment-configured context
print("Creating context from environment variables...")
print(f"  TYPESHED_PATH: {os.getenv('TYPESHED_PATH', 'not set')}")
print(f"  STUB_SEARCH_PATH: {os.getenv('STUB_SEARCH_PATH', 'not set')}")
print(f"  PYTHON_VERSION: {os.getenv('PYTHON_VERSION', 'not set')}")
print(f"  TARGET_PLATFORM: {os.getenv('TARGET_PLATFORM', 'not set')}")

ctx = create_context_from_env()
print(f"\nCreated context:")
print(f"  Version: {ctx.version}")
print(f"  Platform: {ctx.platform}")

Pattern: Validation Context

from typeshed_client import get_search_context, get_stub_file, get_stub_names
from typeshed_client.parser import InvalidStub
from pathlib import Path

def validate_stubs(stub_dir: Path, strict: bool = True):
    """Validate all stubs in a directory."""
    ctx = get_search_context(
        search_path=[stub_dir],
        raise_on_warnings=strict
    )
    
    results = {
        'valid': [],
        'invalid': [],
        'missing': [],
        'total': 0,
    }
    
    # Find all .pyi files
    if not stub_dir.exists():
        print(f"Directory does not exist: {stub_dir}")
        return results
    
    pyi_files = list(stub_dir.rglob('*.pyi'))
    results['total'] = len(pyi_files)
    
    print(f"Validating {len(pyi_files)} stub files...")
    
    for pyi_file in pyi_files:
        # Convert file path to module name
        relative = pyi_file.relative_to(stub_dir)
        module_parts = list(relative.parts[:-1])  # Exclude filename
        if relative.stem != '__init__':
            module_parts.append(relative.stem)
        module_name = '.'.join(module_parts) if module_parts else relative.stem
        
        try:
            names = get_stub_names(module_name, search_context=ctx)
            if names is not None:
                results['valid'].append(module_name)
            else:
                results['missing'].append(module_name)
        except InvalidStub as e:
            results['invalid'].append((module_name, str(e)))
        except Exception as e:
            results['invalid'].append((module_name, f"Unexpected error: {e}"))
    
    return results

# Example usage (commented out - requires actual stub directory)
# stub_directory = Path('./my_stubs')
# validation = validate_stubs(stub_directory, strict=True)
# print(f"\nValidation results:")
# print(f"  Total files: {validation['total']}")
# print(f"  Valid: {len(validation['valid'])}")
# print(f"  Invalid: {len(validation['invalid'])}")
# print(f"  Missing: {len(validation['missing'])}")

Context Reuse

SearchContext objects are immutable and can be reused safely:

from typeshed_client import get_search_context, get_stub_file, get_stub_names, Resolver

# Create context once
ctx = get_search_context(version=(3, 11), platform='linux')

# Reuse across multiple operations
path1 = get_stub_file('typing', search_context=ctx)
path2 = get_stub_file('collections', search_context=ctx)

names1 = get_stub_names('typing', search_context=ctx)
names2 = get_stub_names('collections', search_context=ctx)

resolver = Resolver(search_context=ctx)
info1 = resolver.get_fully_qualified_name('typing.List')
info2 = resolver.get_fully_qualified_name('collections.OrderedDict')

print("✓ All operations use the same context configuration")
print(f"  Version: {ctx.version}")
print(f"  Platform: {ctx.platform}")

Benefits of Reuse:

  • Consistent configuration across operations
  • More efficient (no need to recreate)
  • Thread-safe (immutable)
  • Clear intent (explicit configuration)

Default Context Behavior

When no search context is provided, functions use a default context with:

  • Bundled typeshed
  • Current sys.path
  • Current Python version
  • Current platform
  • Warnings logged (not raised)
  • Only .pyi files searched
from typeshed_client import get_stub_file, get_search_context

# Uses default context
path = get_stub_file('typing')

# Equivalent to:
ctx = get_search_context()
path = get_stub_file('typing', search_context=ctx)

print("Default context settings:")
print(f"  Version: {ctx.version}")
print(f"  Platform: {ctx.platform}")
print(f"  Raise on warnings: {ctx.raise_on_warnings}")
print(f"  Allow .py files: {ctx.allow_py_files}")

Error Handling

ValueError: Conflicting Parameters

from typeshed_client import get_search_context
from pathlib import Path

try:
    # This will raise ValueError
    ctx = get_search_context(
        python_executable='/usr/bin/python3',
        search_path=[Path('./stubs')]  # Cannot specify both
    )
except ValueError as e:
    print(f"✗ Error: {e}")
    print("  Use either python_executable OR search_path, not both")
    
    # Use one or the other
    ctx1 = get_search_context(search_path=[Path('./stubs')])
    print(f"✓ Created context with search_path")
    
    ctx2 = get_search_context(python_executable='/usr/bin/python3')
    print(f"✓ Created context with python_executable")

Missing Executable

from typeshed_client import get_search_context

try:
    ctx = get_search_context(python_executable='/nonexistent/python')
except (ValueError, FileNotFoundError) as e:
    print(f"✗ Error: {e}")
    print("  Python executable not found")
    
    # Fall back to default
    ctx = get_search_context()
    print(f"✓ Using default context")

Invalid Typeshed Path

from pathlib import Path
from typeshed_client import get_search_context, get_stub_file

# Invalid typeshed path doesn't raise immediately
bad_typeshed = Path('/nonexistent/typeshed')
ctx = get_search_context(typeshed=bad_typeshed)
print(f"✓ Created context with invalid typeshed (no immediate error)")

# Error occurs when trying to use it
path = get_stub_file('typing', search_context=ctx)
if path is None:
    print(f"✗ Could not find stub (invalid typeshed path)")
else:
    print(f"✓ Found stub: {path}")

Best Practices for Agents

Context Reuse

  1. Reuse contexts: Create a SearchContext once and reuse it across multiple operations for consistency
# CORRECT - efficient and consistent
ctx = get_search_context(version=(3, 11))
for module in ['typing', 'collections', 'asyncio']:
    names = get_stub_names(module, search_context=ctx)
    # Process names

# INCORRECT - creates new context each time
for module in ['typing', 'collections', 'asyncio']:
    ctx = get_search_context(version=(3, 11))  # Wasteful
    names = get_stub_names(module, search_context=ctx)

Version-Specific Analysis

  1. Version-specific analysis: When analyzing code for different Python versions, create separate contexts
# CORRECT - separate contexts for different versions
ctx_39 = get_search_context(version=(3, 9))
ctx_311 = get_search_context(version=(3, 11))

names_39 = get_stub_names('typing', search_context=ctx_39)
names_311 = get_stub_names('typing', search_context=ctx_311)

Platform Awareness

  1. Platform awareness: Use platform-specific contexts when analyzing platform-dependent code
# CORRECT - explicit platform configuration
ctx_linux = get_search_context(platform='linux')
ctx_windows = get_search_context(platform='win32')

# Analyze platform-specific APIs
names_linux = get_stub_names('os', search_context=ctx_linux)
names_windows = get_stub_names('os', search_context=ctx_windows)

Strict Parsing

  1. Strict parsing for validation: Use raise_on_warnings=True when validating stubs
# CORRECT - strict mode for validation
ctx_strict = get_search_context(raise_on_warnings=True)

from typeshed_client.parser import InvalidStub
try:
    names = get_stub_names('module', search_context=ctx_strict)
    print("✓ Stub is valid")
except InvalidStub as e:
    print(f"✗ Stub has issues: {e}")

Search Paths

  1. Custom search paths: When working with third-party packages, include their installation directories in search_path
# CORRECT - include package directories
from pathlib import Path

venv_packages = Path('./venv/lib/python3.11/site-packages')
ctx = get_search_context(search_path=[venv_packages])

Python Files

  1. Allow .py files carefully: Only set allow_py_files=True when you specifically need to analyze Python source files
# CORRECT - explicit when needed
ctx_with_py = get_search_context(allow_py_files=True)
path = get_stub_file('typed_package', search_context=ctx_with_py)

# CORRECT - default for normal stub analysis
ctx_stubs = get_search_context(allow_py_files=False)  # Or just omit parameter

Documentation

  1. Document context configuration: When context configuration affects behavior, document it clearly
# CORRECT - documented configuration
# Configure for Python 3.9 with strict parsing for validation
ctx = get_search_context(
    version=(3, 9),
    raise_on_warnings=True,
    allow_py_files=False
)
print(f"Using Python {ctx.version} stubs with strict parsing")

Testing

  1. Test with multiple versions: For library development, test with contexts for multiple Python versions
# CORRECT - comprehensive version testing
def test_stub_compatibility(module_name: str):
    versions = [(3, 8), (3, 9), (3, 10), (3, 11), (3, 12)]
    for version in versions:
        ctx = get_search_context(version=version)
        names = get_stub_names(module_name, search_context=ctx)
        print(f"Python {version}: {len(names) if names else 0} names")

Environment Integration

  1. Environment integration: Consider using environment variables to configure contexts in different environments
# CORRECT - environment-aware configuration
import os

version_str = os.getenv('PYTHON_VERSION', '3.11')
major, minor = map(int, version_str.split('.'))

ctx = get_search_context(
    version=(major, minor),
    platform=os.getenv('TARGET_PLATFORM', 'linux')
)

Factory Pattern

  1. Cache contexts: If creating many similar contexts, consider using a factory pattern to reduce code duplication
# CORRECT - factory for common configurations
class StubContextFactory:
    def __init__(self, base_path: Path):
        self.base_path = base_path
    
    def for_version(self, version):
        return get_search_context(
            version=version,
            search_path=[self.base_path]
        )

factory = StubContextFactory(Path('./stubs'))
ctx_39 = factory.for_version((3, 9))
ctx_311 = factory.for_version((3, 11))

Performance Considerations

Context Creation: Fast; no I/O operations Context Reuse: No performance penalty; contexts are lightweight Search Path Length: Longer paths increase stub lookup time Typeshed Size: Larger typeshed directories may affect startup time Version/Platform: No performance impact on context creation

Optimization Tips:

  • Minimize search path length
  • Reuse contexts across operations
  • Use specific search paths instead of entire sys.path when possible