Python supercharged for fastai development
56
Command-line argument parsing, documentation tools, file system utilities, and development aids that bridge fastcore functionality with system-level operations. This module combines script.py, docments.py, and related utilities for building command-line applications and development tools.
Advanced argument parsing system that automatically generates CLI interfaces from Python function signatures.
def call_parse(func=None, **kwargs):
"""
Parse command-line arguments and call function with parsed values.
Automatically creates argument parser from function signature and
docstrings, then calls the function with parsed command-line arguments.
This is the main entry point for converting functions into CLI tools.
Parameters:
- func: function to convert to CLI (uses caller if None)
- **kwargs: additional arguments for argument parser
Usage:
def main(input_file, output_file=None, verbose=False):
'''Process input file and create output.
input_file: Path to input file
output_file: Path to output file (optional)
verbose: Enable verbose logging
'''
# Function implementation here
pass
if __name__ == "__main__":
call_parse(main)
"""
class Param:
"""
Parameter specification for command-line arguments.
Provides detailed control over how function parameters are converted
to command-line arguments, including type conversion, validation,
and help text generation.
Parameters:
- help: str, help text for the parameter
- type: callable, type conversion function
- opt: bool, whether parameter is optional (flag-style)
- action: str, argparse action ('store_true', 'store_false', etc.)
- nargs: str|int, number of arguments to consume
- const: value for const action
- choices: list, allowed values for parameter
- required: bool, whether parameter is required
- default: default value for parameter
- version: str, version string for --version
Usage:
def process_data(
input_file: Param("Input file path", type=str),
verbose: Param("Enable verbose output", action='store_true'),
format: Param("Output format", choices=['json', 'csv', 'xml'], default='json')
):
pass
"""
def __init__(self, help="", type=None, opt=True, action=None, nargs=None,
const=None, choices=None, required=None, default=None, version=None): ...
def set_default(self, d): ...
@property
def pre(self): ...
@property
def kwargs(self): ...
def anno_parser(func, prog=None):
"""
Create ArgumentParser from function annotations and docstrings.
Analyzes function signature, type hints, and docstrings to automatically
generate comprehensive argument parser with proper help text and validation.
Parameters:
- func: function to analyze
- prog: str, program name (auto-detected if None)
Returns:
argparse.ArgumentParser: Configured parser ready for use
"""
def args_from_prog(func, prog):
"""
Extract arguments from program string for testing.
Parses specially formatted program strings to extract argument
values for testing command-line interfaces programmatically.
Parameters:
- func: function being tested
- prog: str, program string with embedded arguments
Returns:
dict: Extracted argument values
"""
def store_true():
"""Placeholder for store_true action in Param definitions."""
def store_false():
"""Placeholder for store_false action in Param definitions."""
def bool_arg(v):
"""
Type converter for boolean command-line arguments.
Converts string arguments to boolean values with flexible parsing
that handles various common boolean representations.
Parameters:
- v: str, string value to convert
Returns:
bool: Converted boolean value
Accepts: true/false, yes/no, 1/0, on/off (case-insensitive)
"""Tools for extracting and processing documentation from Python code.
def docstring(sym):
"""
Extract docstring from symbol (function, class, or module).
Retrieves docstring with fallback to __init__ method for classes
and proper handling of various symbol types.
Parameters:
- sym: function, class, module, or string
Returns:
str: Extracted docstring or empty string
"""
def parse_docstring(sym):
"""
Parse numpy-style docstring into structured components.
Extracts and structures docstring components including parameters,
returns, examples, and other sections using numpy docstring format.
Parameters:
- sym: function, class, or docstring to parse
Returns:
AttrDict: Structured docstring components
"""
def docments(elt, full=False, **kwargs):
"""
Extract parameter documentation from function comments and annotations.
Analyzes function source code to extract parameter documentation
from both docstrings and inline comments, creating comprehensive
parameter information for API documentation.
Parameters:
- elt: function or class to document
- full: bool, include all parameter details
- **kwargs: additional options for extraction
Returns:
dict: Parameter documentation mapping names to details
"""
def get_source(s):
"""
Get source code for function, class, or dataclass.
Retrieves source code with proper handling of different object types
including functions, methods, and dataclasses.
Parameters:
- s: str|function|class, object to get source for
Returns:
str: Source code or None if unavailable
"""
def get_name(s):
"""
Get qualified name for object.
Returns the fully qualified name including module and class context
for proper object identification.
Parameters:
- s: object to get name for
Returns:
str: Qualified name
"""
def qual_name(o):
"""
Get qualified name with module information.
Parameters:
- o: object to get qualified name for
Returns:
str: Fully qualified name including module
"""
def sig2str(sig):
"""
Convert function signature to string representation.
Parameters:
- sig: inspect.Signature object
Returns:
str: String representation of signature
"""
def extract_docstrings(source):
"""
Extract all docstrings from Python source code.
Parses source code to find and extract all docstrings including
module, class, and function docstrings with location information.
Parameters:
- source: str, Python source code
Returns:
dict: Mapping of locations to docstrings
"""Helper functions and classes for development workflow and code analysis.
def isdataclass(s):
"""
Check if object is a dataclass class (not instance).
Parameters:
- s: object to check
Returns:
bool: True if s is dataclass class
"""
def get_dataclass_source(s):
"""
Get source code for dataclass with special handling.
Parameters:
- s: dataclass to get source for
Returns:
str: Source code or empty string
"""
def clean_type_str(x):
"""
Clean up type string representation for display.
Removes verbose type information and formatting for cleaner
display in help text and documentation.
Parameters:
- x: str, type string to clean
Returns:
str: Cleaned type string
"""
empty = Parameter.empty
"""Sentinel value representing empty/missing parameters."""
SCRIPT_INFO = {}
"""Global dictionary for storing script information and metadata."""from fastcore.script import call_parse, Param
# Simple CLI application
def process_file(
input_file: Param("Path to input file", type=str),
output_file: Param("Path to output file", type=str, default="output.txt"),
verbose: Param("Enable verbose output", action='store_true'),
format: Param("Output format", choices=['json', 'csv', 'xml'], default='json')
):
"""
Process input file and generate output in specified format.
This tool reads the input file, processes the data, and writes
the results to the output file in the specified format.
"""
if verbose:
print(f"Processing {input_file}...")
print(f"Output format: {format}")
# Processing logic here
with open(input_file, 'r') as f:
data = f.read()
# Transform data based on format
if format == 'json':
import json
result = json.dumps({"data": data, "processed": True})
elif format == 'csv':
result = f"data,processed\n{data},true"
else: # xml
result = f"<root><data>{data}</data><processed>true</processed></root>"
with open(output_file, 'w') as f:
f.write(result)
if verbose:
print(f"Output written to {output_file}")
if __name__ == "__main__":
call_parse(process_file)
# Usage from command line:
# python script.py input.txt --output-file result.json --verbose --format jsonfrom fastcore.script import call_parse, Param, bool_arg
def data_analysis(
dataset: Param("Dataset file path"),
model_type: Param("ML model type", choices=['linear', 'tree', 'neural'], default='linear'),
train_size: Param("Training set size", type=float, default=0.8),
random_seed: Param("Random seed for reproducibility", type=int, default=42),
normalize: Param("Normalize features", type=bool_arg, default=True),
output_dir: Param("Output directory", default="./results"),
verbose: Param("Verbose output", action='store_true'),
debug: Param("Debug mode", action='store_true'),
config_file: Param("Configuration file", required=False),
gpu_count: Param("Number of GPUs to use", type=int, nargs='?', const=1, default=0)
):
"""
Perform data analysis with machine learning models.
Analyzes the provided dataset using the specified model type
and configuration parameters. Results are saved to the output directory.
"""
import os
# Validate inputs
if not os.path.exists(dataset):
raise FileNotFoundError(f"Dataset file not found: {dataset}")
if train_size <= 0 or train_size >= 1:
raise ValueError("Train size must be between 0 and 1")
# Setup output directory
os.makedirs(output_dir, exist_ok=True)
config = {
'model_type': model_type,
'train_size': train_size,
'random_seed': random_seed,
'normalize': normalize,
'gpu_count': gpu_count
}
if config_file:
import json
with open(config_file, 'r') as f:
file_config = json.load(f)
config.update(file_config)
if verbose or debug:
print("Configuration:")
for key, value in config.items():
print(f" {key}: {value}")
# Analysis logic would go here
print(f"Analyzing {dataset} with {model_type} model...")
# Save results
results_file = os.path.join(output_dir, "results.json")
import json
with open(results_file, 'w') as f:
json.dump(config, f, indent=2)
print(f"Results saved to {results_file}")
if __name__ == "__main__":
call_parse(data_analysis)
# Command line usage examples:
# python analysis.py data.csv --model-type neural --normalize true --verbose
# python analysis.py data.csv --train-size 0.7 --gpu-count --debug
# python analysis.py data.csv --config-file config.json --output-dir results/from fastcore.docments import docstring, parse_docstring, docments, get_source
def analyze_function_documentation(func):
"""Comprehensive analysis of function documentation."""
# Extract basic docstring
doc = docstring(func)
print(f"Docstring: {doc}")
# Parse structured docstring
parsed = parse_docstring(func)
print(f"Summary: {parsed.get('Summary', 'No summary')}")
print(f"Parameters: {parsed.get('Parameters', 'No parameters documented')}")
# Extract detailed parameter documentation
param_docs = docments(func, full=True)
print("\nDetailed parameter information:")
for name, info in param_docs.items():
print(f" {name}: {info.get('docment', 'No documentation')}")
if hasattr(info, 'anno') and info.anno:
print(f" Type: {info.anno}")
if hasattr(info, 'default') and info.default is not None:
print(f" Default: {info.default}")
# Get source code
source = get_source(func)
if source:
print(f"\nSource code:\n{source}")
# Example function to analyze
def example_function(
data: list,
threshold: float = 0.5,
normalize: bool = True
) -> dict:
"""
Process data with threshold filtering.
Parameters
----------
data : list
Input data to process
threshold : float, optional
Filtering threshold (default: 0.5)
normalize : bool, optional
Whether to normalize results (default: True)
Returns
-------
dict
Processed results with statistics
Examples
--------
>>> result = example_function([1, 2, 3, 4, 5])
>>> print(result['count'])
5
"""
filtered = [x for x in data if x > threshold]
if normalize:
total = sum(filtered)
filtered = [x/total for x in filtered]
return {
'data': filtered,
'count': len(filtered),
'normalized': normalize
}
# Analyze the function
analyze_function_documentation(example_function)from fastcore.docments import extract_docstrings, sig2str
from fastcore.script import clean_type_str
import inspect
class APIDocumentationGenerator:
"""Generate API documentation from Python modules."""
def __init__(self, module):
self.module = module
self.functions = []
self.classes = []
# Analyze module contents
for name in dir(module):
obj = getattr(module, name)
if not name.startswith('_'):
if inspect.isfunction(obj):
self.functions.append((name, obj))
elif inspect.isclass(obj):
self.classes.append((name, obj))
def generate_function_doc(self, name, func):
"""Generate documentation for a function."""
sig = inspect.signature(func)
doc = docstring(func)
# Clean up signature display
params = []
for param_name, param in sig.parameters.items():
param_str = param_name
if param.annotation != inspect.Parameter.empty:
type_str = clean_type_str(param.annotation)
param_str += f": {type_str}"
if param.default != inspect.Parameter.empty:
param_str += f" = {param.default}"
params.append(param_str)
signature = f"{name}({', '.join(params)})"
if sig.return_annotation != inspect.Parameter.empty:
return_type = clean_type_str(sig.return_annotation)
signature += f" -> {return_type}"
return {
'name': name,
'signature': signature,
'docstring': doc,
'source_file': inspect.getfile(func) if hasattr(func, '__file__') else None
}
def generate_class_doc(self, name, cls):
"""Generate documentation for a class."""
doc = docstring(cls)
methods = []
for method_name in dir(cls):
if not method_name.startswith('_') or method_name == '__init__':
method = getattr(cls, method_name)
if inspect.isfunction(method) or inspect.ismethod(method):
methods.append(self.generate_function_doc(method_name, method))
return {
'name': name,
'docstring': doc,
'methods': methods,
'source_file': inspect.getfile(cls) if hasattr(cls, '__file__') else None
}
def generate_markdown(self):
"""Generate markdown documentation."""
lines = [f"# {self.module.__name__} API Documentation\n"]
if self.module.__doc__:
lines.append(f"{self.module.__doc__}\n")
if self.functions:
lines.append("## Functions\n")
for name, func in self.functions:
doc = self.generate_function_doc(name, func)
lines.append(f"### {doc['signature']}\n")
if doc['docstring']:
lines.append(f"{doc['docstring']}\n")
lines.append("")
if self.classes:
lines.append("## Classes\n")
for name, cls in self.classes:
doc = self.generate_class_doc(name, cls)
lines.append(f"### class {doc['name']}\n")
if doc['docstring']:
lines.append(f"{doc['docstring']}\n")
if doc['methods']:
lines.append("#### Methods\n")
for method in doc['methods']:
lines.append(f"**{method['signature']}**\n")
if method['docstring']:
lines.append(f"{method['docstring']}\n")
lines.append("")
return '\n'.join(lines)
# Generate documentation for fastcore.basics
import fastcore.basics
doc_gen = APIDocumentationGenerator(fastcore.basics)
markdown_docs = doc_gen.generate_markdown()
# Save to file
with open('api_docs.md', 'w') as f:
f.write(markdown_docs)
print("API documentation generated in api_docs.md")from fastcore.script import args_from_prog, call_parse
import subprocess
import tempfile
import os
def test_cli_application():
"""Test CLI application programmatically."""
def sample_app(
input_file: str,
output_file: str = "output.txt",
verbose: bool = False
):
"""Sample application for testing."""
if verbose:
print(f"Processing {input_file} -> {output_file}")
with open(input_file, 'r') as f:
content = f.read()
with open(output_file, 'w') as f:
f.write(f"Processed: {content}")
return output_file
# Test with temporary files
with tempfile.TemporaryDirectory() as temp_dir:
# Create test input file
input_path = os.path.join(temp_dir, "input.txt")
with open(input_path, 'w') as f:
f.write("Test content")
# Test different argument combinations
test_cases = [
# Basic usage
{
'args': [input_path],
'expected_output': 'output.txt'
},
# Custom output file
{
'args': [input_path, '--output-file', 'custom.txt'],
'expected_output': 'custom.txt'
},
# Verbose mode
{
'args': [input_path, '--verbose'],
'expected_output': 'output.txt'
}
]
for i, test_case in enumerate(test_cases):
print(f"Running test case {i + 1}: {test_case['args']}")
# Simulate command-line execution
import sys
old_argv = sys.argv
try:
sys.argv = ['test_script.py'] + test_case['args']
result = call_parse(sample_app)
# Verify output file was created
expected_file = test_case['expected_output']
if os.path.exists(expected_file):
with open(expected_file, 'r') as f:
content = f.read()
assert "Processed: Test content" in content
print(f"✓ Test case {i + 1} passed")
os.remove(expected_file) # Cleanup
else:
print(f"✗ Test case {i + 1} failed: output file not found")
finally:
sys.argv = old_argv
# Run the test
test_cli_application()from fastcore.script import call_parse, Param
from fastcore.parallel import parallel
from fastcore.foundation import L
from fastcore.xtras import walk
from fastcore.net import urlread
import json
def web_scraper_cli(
urls_file: Param("File containing URLs to scrape"),
output_dir: Param("Output directory for scraped content", default="./scraped"),
workers: Param("Number of parallel workers", type=int, default=4),
delay: Param("Delay between requests (seconds)", type=float, default=1.0),
format: Param("Output format", choices=['json', 'txt'], default='json'),
verbose: Param("Verbose output", action='store_true')
):
"""
Scrape content from URLs in parallel using FastCore utilities.
Reads URLs from file, scrapes content in parallel, and saves results
in the specified format. Demonstrates integration of multiple FastCore
components in a real application.
"""
import os
from urllib.parse import urlparse
import time
# Create output directory
os.makedirs(output_dir, exist_ok=True)
# Read URLs from file
with open(urls_file, 'r') as f:
urls = [line.strip() for line in f if line.strip()]
if verbose:
print(f"Found {len(urls)} URLs to scrape")
print(f"Using {workers} parallel workers")
def scrape_url(url):
"""Scrape a single URL with error handling."""
try:
if delay > 0:
time.sleep(delay)
content = urlread(url)
# Generate filename from URL
parsed = urlparse(url)
filename = f"{parsed.netloc}_{parsed.path.replace('/', '_')}"
if not filename.endswith('.txt') and format == 'txt':
filename += '.txt'
elif not filename.endswith('.json') and format == 'json':
filename += '.json'
filepath = os.path.join(output_dir, filename)
# Save content
if format == 'json':
data = {
'url': url,
'content': content,
'scraped_at': time.time()
}
with open(filepath, 'w') as f:
json.dump(data, f, indent=2)
else:
with open(filepath, 'w') as f:
f.write(f"URL: {url}\n")
f.write(f"Content:\n{content}")
if verbose:
print(f"✓ Scraped {url} -> {filename}")
return {'url': url, 'file': filepath, 'success': True}
except Exception as e:
if verbose:
print(f"✗ Failed to scrape {url}: {e}")
return {'url': url, 'error': str(e), 'success': False}
# Scrape URLs in parallel
results = parallel(scrape_url, urls, n_workers=workers, progress=verbose)
# Generate summary
successful = L(results).filter(lambda x: x['success'])
failed = L(results).filter(lambda x: not x['success'])
summary = {
'total_urls': len(urls),
'successful': len(successful),
'failed': len(failed),
'output_directory': output_dir,
'files_created': [r['file'] for r in successful if 'file' in r]
}
# Save summary
summary_file = os.path.join(output_dir, 'scraping_summary.json')
with open(summary_file, 'w') as f:
json.dump(summary, f, indent=2)
print(f"\nScraping completed!")
print(f"Successful: {summary['successful']}/{summary['total_urls']}")
print(f"Results saved in: {output_dir}")
if failed:
print(f"Failed URLs: {len(failed)}")
for result in failed:
print(f" {result['url']}: {result['error']}")
if __name__ == "__main__":
call_parse(web_scraper_cli)
# Usage:
# python scraper.py urls.txt --workers 8 --format json --verbose
# python scraper.py urls.txt --output-dir ./data --delay 2.0Install with Tessl CLI
npx tessl i tessl/pypi-fastcoredocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10